https://www.gravatar.com/avatar/7a0c24f697ea1587001c36d00039b60f?s=240&d=mp

LinearLayout实现横向跑马灯效果

开发过程中,我们常常要实现各种各样的效果,跑马灯就是其中之一。android的TextView可以设置属性android:ellipsize="marquee"来实现跑马灯效果,但是如果我们的是要实现图文并茂的复杂布局的跑马灯,TextView就有点捉襟见肘了。

先来看下效果图:

http://7xq9ge.com1.z0.glb.clouddn.com/FqJaX95ZDTGut5sKeX97hmf2RUjS

既有图片,又有文字。我们看到这种效果,一般会想到通过自定义控件来实现,但是处理起来还是有点麻烦。那么我们可以怎样来实现呢?

ObjectAnimator

Android属性动画。跑马灯实际就是一个横向的位移效果。我们完全可以通过通过给View设置动画来实现,而且非常简单。

ObjectAnimator是从API11,也就是android3.0开始引入的。网上有很多相关的文章了,这里就不详细介绍了。我们只关注我们需要实现动画的方法。

ofFloat(Object target, String propertyName, float... values) Constructs and returns an ObjectAnimator that animates between float values.

第一个参数: 需要执行动画的View 第二个参数: 动画类型,因为我们是需要执行横向移动效果,所以直接传入"translationX" 第三个参数: 这是一个可变的float参数。因为我们是需要从右到左的执行。按照坐标规律,数字是从大到小,因为跑马灯需要滚出视图区域,所以结束位置一定是一个负数。float... values可以只传入一个值,代表从当前位置往右边移动多少。可以自己设置不同的值来看效果。

那么如何实现我们图中的效果呢?控件默认位置是0,需要让它从最右边执行到最左边。

React Native首次运行提示'ReferenceError: Can't find variable: _fbBatchedBridge'

React Native目前貌似要火的样子,作为移动开发人员,当然需要关注最新的开发技术,所以也就跟风试了试,但是发现运行的时候红屏报错,谷歌了一下,发现官网给出了解决方案,步骤如下:

  1. 确保你的电脑和手机处于同一个wifi网络
  2. 从手机上启动你的React Native app。
  3. 你看见的是一个堆满error错误的红屏,请按照接下来的步骤修复它。
  4. 通过摇晃手机或者在命令行运行adb shell input keyevent 82打开开发者菜单,如下图所示 http://7xq9ge.com1.z0.glb.clouddn.com/Fo4vETWlkxRcIpmV-QSu59mO5jWP
  5. 进入Dev Settings.
  6. 点击Debug server host for device
  7. 手动键入你电脑的IP地址和本地开发服务器的端口,默认端口是的8081(e.g. 10.0.1.1:8081) 查看电脑IP的方法:
  • Mac: 系统偏好设置-网络
  • Windows: 打开命令行,输入ipconfig
  1. 返回后重新选择Reload JS

如果一切正常,应该就能正常加载显示了。Gook luck!

Android GC 那点事

想写一篇关于Android GC的想法来源于追查一个魅族手机图片滑动卡顿问题,由于不断的GC导致的丢帧卡顿的问题让我们想了很多方案去解决,所以就打算详细的看看内存分配和GC的原理,为什么会不断的GC, GC ALLOC和GC COCURRENT有什么区别,能不能想办法扩大堆内存减少GC的频次等等。

1. JVM内存回收机制

1.1. 回收算法

标记回收算法(Mark and Sweep GC)

从"GC Roots"集合开始,将内存整个遍历一次,保留所有可以被GC Roots直接或间接引用到的对象,而剩下的对象都当作垃圾对待并回收,这个算法需要中断进程内其它组件的执行并且可能产生内存碎片。

复制算法 (Copying)

将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

标记-压缩算法 (Mark-Compact)

先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。

分代

将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。对于新生代适用于复制算法,而对于老年代则采取标记-压缩算法。

1.2. 复制和标记-压缩算法的区别

乍一看这两个算法似乎并没有多大的区别,都是标记了然后挪到另外的内存地址进行回收,那为什么不同的分代要使用不同的回收算法呢? 其实2者最大的区别在于前者是用空间换时间后者则是用时间换空间。 前者的在工作的时候是不没有独立的“Mark”与“Copy”阶段的,而是合在一起做一个动作,就叫Scavenge(或Evacuate,或者就叫Copy)。也就是说,每发现一个这次收集中尚未访问过的活对象就直接Copy到新地方,同时设置Forwarding Pointer,这样的工作方式就需要多一份空间。 后者在工作的时候则需要分别的Mark与Compact阶段,Mark阶段用来发现并标记所有活的对象,然后compact阶段才移动对象来达到Compact的目的。如果Compact方式是Sliding Compaction,则在Mark之后就可以按顺序一个个对象“滑动”到空间的某一侧。因为已经先遍历了整个空间里的对象图,知道所有的活对象了,所以移动的时候就可以在同一个空间内而不需要多一份空间。 所以新生代的回收会更快一点,老年代的回收则会需要更长时间,同时压缩阶段是会暂停应用的,所以给我们应该尽量避免对象出现在老年代。

2. Dalvik虚拟机

2.1. Java堆

Java堆实际上是由一个Active堆和一个Zygote堆组成的,其中,Zygote堆用来管理Zygote进程在启动过程中预加载和创建的各种对象,而Active堆是在Zygote进程Fork第一个子进程之前创建的。以后启动的所有应用程序进程是被Zygote进程Fork出来的,并都持有一个自己的Dalvik虚拟机。在创建应用程序的过程中,Dalvik虚拟机采用Cow策略复制Zygote进程的地址空间。 Cow策略:一开始的时候(未复制Zygote进程的地址空间的时候),应用程序进程和Zygote进程共享了同一个用来分配对象的堆。当Zygote进程或者应用程序进程对该堆进行写操作时,内核就会执 行真正的拷贝操作,使得Zygote进程和应用程序进程分别拥有自己的一份拷贝,这就是所谓的Cow。因为Copy是十分耗时的,所以必须尽量避免Copy或者尽量少的Copy。 为了实现这个目的,当创建第一个应用程序进程时,会将已经使用了的那部分堆内存划分为一部分,还没有使用的堆内存划分为另外一部分。前者就称为Zygote堆,后者就称为Active堆。这样只需把zygote堆中的内容复制给应用程序进程就可以了。以后无论是Zygote进程,还是应用程序进程,当它们需要分配对象的时候,都在Active堆上进行。这样就可以使得Zygote堆尽可能少地被执行写操作,因而就可以减少执行写时拷贝的操作。在Zygote堆里面分配的对象其实主要就是Zygote进程在启动过程中预加载的类、资源和对象了。这意味着这些预加载的类、资源和对象可以在Zygote进程和应用程序进程中做到长期共享。这样既能减少拷贝操作,还能减少对内存的需求。

2.2. 和GC有关的一些指标

记得我们之前在优化魅族某手机的gc卡顿问题时,发现他很容易触发GC_FOR_MALLOC,这个GC类别后续会说到,是分配对象内存不足时导致的。可是我们又设置了很大的堆Size为什么还会内存不够呢,这里需要了解以下几个概念:分别是Java堆的起始大小(Starting Size)、最大值(Maximum Size)和增长上限值(Growth Limit)。 在启动Dalvik虚拟机的时候,我们可以分别通过-Xms、-Xmx和-XX:HeapGrowthLimit三个选项来指定上述三个值,以上三个值分别表示表示: Starting Size: Dalvik虚拟机启动的时候,会先分配一块初始的堆内存给虚拟机使用。 Growth Limit: 是系统给每一个程序的最大堆上限,超过这个上限,程序就会OOM。 Maximum Size: 不受控情况下的最大堆内存大小,起始就是我们在用largeheap属性的时候,可以从系统获取的最大堆大小。 同时除了上面的这个三个指标外,还有几个指标也是值得我们关注的,那就是堆最小空闲值(Min Free)、堆最大空闲值(Max Free)和堆目标利用率(Target Utilization)。假设在某一次GC之后,存活对象占用内存的大小为LiveSize,那么这时候堆的理想大小应该为(LiveSize / U)。但是(LiveSize / U)必须大于等于(LiveSize + MinFree)并且小于等于(LiveSize + MaxFree),每次GC后垃圾回收器都会尽量让堆的利用率往目标利用率靠拢。所以当我们尝试手动去生成一些几百K的对象,试图去扩大可用堆大小的时候,反而会导致频繁的GC,因为这些对象的分配会导致GC,而GC后会让堆内存回到合适的比例,而我们使用的局部变量很快会被回收理论上存活对象还是那么多,我们的堆大小也会缩减回来无法达到扩充的目的。 与此同时这也是产生CONCURRENT GC的一个因素,后文我们会详细讲到。

Android无需权限显示悬浮窗, 兼谈逆向分析app

如下图, 截图是在使用Chrome时截的, 但是屏幕顶部却有UC的view浮在屏幕上. 我使用的是小米, 我并没有给UC授悬浮窗权限, 所以我看到这个悬浮窗时是很震惊的.

http://ww4.sinaimg.cn/large/62b0904dgw1f04v52p27dj20c80lqabw.jpg

悬浮窗原理

做过悬浮窗功能的人都知道, 要想显示悬浮窗, 要有一个服务运行在后台, 通过getSystemService(Context.WINDOW_SERVICE)拿到WindowManager, 然后向其中addView, addView第二个参数是一个WindowManager.LayoutParams, WindowManager.LayoutParams中有一个成员type, 有各种值, 一般设置成TYPE_PHONE就可以悬浮在很多view的上方了, 但是调用这个方法需要申请android.permission.SYSTEM_ALERT_WINDOW权限, 在很多机型上, 这个权限的名字叫悬浮窗, 比如小米手机上默认是禁用这个权限的, 有些恶意app会用这个权限弹广告, 而且很难追查是哪个应用弹的. 如果这个权限被禁用, 那么结果就是悬浮窗无法展示, 比如有道词典的复制查词功能, 在小米手机上经常没用, 其实是用户没有授权, 而且应用也没有引导用户给它打开授权.

那么他是怎么实现的呢?有人就进行了逆向分析。

过程省略。。。直接说结论

验证

实际测试了一下, 将type设置成TYPE_TOAST果然有奇效, 不需要android.permission.SYSTEM_ALERT_WINDOW权限就能显示一个悬浮窗.

之前我一直以为调用了系统WindowManager.addView需要android.permission.SYSTEM_ALERT_WINDOW权限, 但实际上调用这个方法是不需要权限的, 在Android源码中有这么一段

public int checkAddPermission(WindowManager.LayoutParams attrs) {
    int type = attrs.type;

    if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
            || type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
        return WindowManagerImpl.ADD_OKAY;
    }
    String permission = null;
    switch (type) {
        case TYPE_TOAST:
            // XXX right now the app process has complete control over
            // this...  should introduce a token to let the system
            // monitor/control what they are doing.
            break;
        case TYPE_INPUT_METHOD:
        case TYPE_WALLPAPER:
            // The window manager will check these.
            break;
        case TYPE_PHONE:
        case TYPE_PRIORITY_PHONE:
        case TYPE_SYSTEM_ALERT:
        case TYPE_SYSTEM_ERROR:
        case TYPE_SYSTEM_OVERLAY:
            permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
            break;
        default:
            permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
    }
    if (permission != null) {
        if (mContext.checkCallingOrSelfPermission(permission)
                != PackageManager.PERMISSION_GRANTED) {
            return WindowManagerImpl.ADD_PERMISSION_DENIED;
        }
    }
    return WindowManagerImpl.ADD_OKAY;
}

可以猜到这个方法是往系统的WindowManager里addView的时候做权限检查用的, 那个type就是我们在构造WindowManager.LayoutParams时赋值的type, 可以看到, 除了TYPE_TOAST, 其他都是要权限的, 而且非常喜感的是, 代码中的注释还说他们现在对这种type毫无限制, 应该引入标记来限制开发者.

Git:代码冲突常见解决方法

使用git,当本地文件修改后,使用git pull会报错error: Your local changes to the following files would be overwritten by merge:

此时可以使用如下解决方案

git stash  
git pull  
git stash pop  

但是有一个问题需要注意,合并后会生成类似====>之类的东西,需要手动去处理下,不然会有问题的。

mac 使用 nvm 管理不同版本的 node 与 npm

我最开始通过brew安装的nvm,发现各种坑,经常导致我的终端启动卡死。一堆的资源没有激活之类的提示。然后突然找到一篇文章,说通过brew安装的nvm路径有问题,建议直接全局安装nvm。然后试了下,果然效果拔萃,把brew安装的nvm卸载后,现在终端启动终于正常了。

nvm 是 Mac 下的 node 管理工具,有点类似管理 Ruby 的 rvm,如果是需要管理 Windows 下的 node,官方推荐是使用 nvmw 或 nvm-windows 。

一、卸载已安装到全局的 node/npm

如果之前是在官网下载的 node 安装包,运行后会自动安装在全局目录,其中

node 命令在 /usr/local/bin/node ,npm 命令在全局 node_modules 目录中,具体路径为 /usr/local/lib/node_modules/npm

安装 nvm 之后最好先删除下已安装的 node 和全局 node 模块:

npm ls -g --depth=0 #查看已经安装在全局的模块,以便删除这些全局模块后再按照不同的 node 版本重新进行全局安装

sudo rm -rf /usr/local/lib/node_modules #删除全局 node_modules 目录
sudo rm /usr/local/bin/node #删除 node
cd  /usr/local/bin && ls -l | grep "../lib/node_modules/" | awk '{print $9}'| xargs rm #删除全局 node 模块注册的软链

二、安装 nvm

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.29.0/install.sh | bash

安装完成后请重新打开终端环境,Mac 下推荐使用 oh-my-zsh 代替默认的 bash shell。