保活 先从老式最基础的开始:
- 使用startService方式启动一个独立进程的服务,这样系统会在service意外死亡后自动重启。
- 使用RTC定时闹钟每5分钟检测一下(4.0以上基本无效)
- 启动linux守护进程,每几分钟检测一下进程是否存在,不存在就startService(5.0以下除MIUI和华为外有效)
- 5.0以上使用JobScheduler代替闹钟定时检测启动 。
- 启动隐藏的前台通知。(支付宝即采用该方式,为系统的一个bug,在7.1.1中已修复,具体体现为下拉任务栏可以看到该通知。)但这些措施都不能100%保活。尤其是M中引入的doze模式,在doze模式中甚至无网络连接。
- 官方建议:1.引导用户加入白名单;2.使用Google系列服务如GCM,Firebase
内存三级缓存思路 对于列表页中的图片应使用LRU之类的内存缓存,对于activity和fragment销毁时应该把其对应的图片释放掉。每个图片都是有依赖引用的,我们一般默认是fragment或者是activity,当activity或者fragment销毁时,我们会将只依赖当前页面的图片移出LRU进行释放。
- 内存图片缓存还是有三级:一级是bitmap列表,一级是LRU强引用,一级是弱引用。加载图片时,先在弱引用缓存和LRU强引用中查找没有被释放的bitmap,如果存在并满足复用条件,则不创建bitmap,而使用该图片内存,之后将其从弱引用或者LRU中移出放入bitmap列表强引用中。bitmap列表强引用保存了该图片依赖的直接控件。当view的onDetachedFromWindow被调用则从bitmap列表中移除只有依赖该view的对象到LRU强应用中。如果LRU强引用满了则放到弱引用中。
- 这种方案可以达到如下效果:列表滑动、或者打开新页面时会申请较小的内存或不申请内存。列表页来回滑动时依然有缓存可用。
- 对于adapterView和recycleView中的控件则需单独写个工具方法来封装一下。
- 这里实际上已经不需要弱引用了,只要从LRU中移除即可调用recycle方法释放该图片。
- 图片内存的申请和释放都由框架来控制,不由gc管理。
- LRU的大小就是可调,它里面的图片内存是都可以释放的,它只是作为缓存而存在。
卡顿问题快速定位的方法GPU monitor分析:
- 打开开发者模式中GPU呈现模式分析,查看是那种颜色条高
- 如果是蓝色偏高,说明是单位消息里CPU太耗时,得把方法的执行都打出来看看哪个耗时。比如,在某处先看看是不是应该出现onMeasure,然后可以通过sdk自带的View布局工具,看一下哪个View的onMeasure耗时最多。
- 如果红色偏高,说明GPU忙不过来。优化过渡绘制,使用离屏缓存来优化。
- 黄色偏高,说明半透明GPU不仅在忙着绘制你的window也还忙着绘制别的,可能的情况为透明window叠加多了,window里的contentView有多个且相对复杂,或者GPU降频了等等,想具体分析需要查看GPU的trace。
- 画动画时蓝色偏高是不正常的
**蓝色偏高的常见原因:**1. 动画或者交互时缓存失效的太多,验证方法是打出方法trace看看是不是有很多次的invalidate调用和dispatchDraw耗时在前面。2. 触发了GC,验证看看trace中主线程是不是被莫名的暂停了。3. 触发了layout,这种蓝色会很高,trace中measure方法会耗时较高。
如何优化启动速度 没有闪屏页activity,一旦存在闪屏页activity那么启动速度就不大可能在200ms内跳过。 把window的背景设置为闪屏页,一旦MainActivity加载完毕就显示主页了。 虽然用户也会看到一个类似的闪屏页,但那个闪屏页实际只是activity在theme中设置的background。 之前好像有人问我怎么优化启动速度。这个方案适合启动画面不是作为广告页只是过渡页使用的场景。
<item name="android:windowBackground">@drawable/splash_logo</item>
复制代码
自定义高性能可拖动GridView 在拖动过程中没有触发过invalidate,也没有触发requestLayout,别的应用每次移动动画会触发notifyDateChange,这会触发layout,影响性能。我们在拖动过程中会建立虚拟的视觉关系,只要不松手就会改变子view的顺序,只有松手才触发datechange。 提升性能绘制方法
- 调用方法把它移出可见区域,移出可见区域后,在进行绘制的时候 native 层也不会去绘制它。
TextView优化
- 列表直接不用textview,自定义view,通过提前缓存 StaticLayout提升TextView性能,可参考:http://ragnraok.github.io/textview-pre-render-research.html
Fragment留意点 每个页面都是Fragment,自己管理其生命周期和栈,每次启动是以window的方式添加进来,进入动画为window动画,手势回退为View动画,为了节省内存,页面栈只保留2个对象,FragmentManager会进行回收释放和Fragment的恢复。
- 优点:加载动画非常流畅,内存占用低,支持页面的无穷层级叠加。
- 缺点:以Window方式启动,很多系统的特性需要自己实现,难以驾驭。
状态栏兼容注意点 针对状态栏我们单独适配了4.4以上版本,5.0以上版本,6.0以上版本,Flyme系统,6.0以上的Flyme系统,MIUI系统,6.0以上的MIUI系统,YunOS系统,VIVO Funtouch 2.5以下版本和2.5以上版本.......
try-catch
- 如果你的代码一定会抛出异常,那try catch会有一些影响。
- 一旦捕获到异常,系统打到寄存器中获取当前函数调用栈,生成一堆信息,这总归是浪费性能的。
- 所以说一般不影响,非要扣那肯定影响,本来get方法可以进行内联的,用来try catch肯定就不能进行内联优化了,就会让性能下降一点。
- android这几个版本推出了JIT,art虚拟机中重点对内联函数的范围进行扩充,try catch会阻止这些优化。
- 在android 5.0以上(ART进行OPT优化时)所有函数中存在try catch的方法都不能被JIT优化和进行内联优化
查看应用真实内存
-
adb shell dumpsys meminfo 你的包名 (monitor中不会显示WebView的内存占用)
-
(注意点)开了线程来执行耗时操作,可是这耗时操作执行的时候把主线的CPU占用给抢了。。。。
高绘制性能函数 看一个ListView的函数
offsetChildrenTopAndBottom(int index)
复制代码
这个方法性能很高,但是隐藏方法,listView移动子控件是用这个方法来移动的,它不破坏缓存,系统相当于是做了1+1+1+2,我们自己做就是1+2+1+2+1+2,我们自己写ListView的时候发现了这个函数,我们做for循环的性能还是不如系统的这个隐藏方法。
使用windowManager的addView来添加控件 例子:通过调用Fragment的onCreateView来生成一个View,然后addView进来,这导致跳转界面需要较长时间。现在得先addView一个View到windowManager中,然后在调用onCreateView,因为windowManager添加控件是在server进程,所以会立即addView进来,这时这里的View就需要显示点东西,这就需要类似windowBackground的东西来显示。windowBackground它存在的目的就是为了加快界面响应。
实现自己的windowbackground 先add一个类似decoreView进来,设置decoreView的background为windowBackground,然后在往这个decoreView上添加实际的控件。
避免使用LayoutParams实现动画
-
setLayoutParams()会触发requestLayout()从而导致所有View重新measure、layout、draw,导致卡顿。 排查方法:可以用布局边界排查大小变化的。 例:爱范儿 下拉刷新。(排查方法为:重写顶端控件的requestLayout方法,打上断点,看看动画或者交互过程中谁调用到了这个方法)
-
还有查看谁刷新了页面导致重绘的排查方法是:重写顶端控件的invalidateChildInParent方法,看看谁调用到了。
使用硬件离屏缓存进行优化。(要保证缓存不失效)
正确的使用:显示硬件层更新绿色闪一下。
错误的使用:过程中一直绿色。
错误例:微信大图。
正确例:掌阅首页切换
复制代码
- 硬件加速本质上是属于window级别的东西,在创建ViewRootImpl的时候就确定了是否使用硬件加速,View级别所谓的关闭只是创建一张bitmap然后调用View的draw方法往这个上面绘制,绘制完成再往硬件加速的canvas上绘制。
- 系统对OpenGl方法进行了封装和优化,封装实现了canvas的方法,使用它有的时候比直接使用OpenGl性能还好。所以开硬件加速几乎等效于调用OpenGl接口来绘制,OpenGl是通用绘制接口,一般GPU都会实现这些接口,所以硬件加速是让GPU来绘制,而非硬件加速就是CPU自己绘制。
- CPU要实现那么多的通用计算,而GPU就那么几个简单接口,它就极端优化,所以这几个简单方法的性能非常高。(OpenGl标准方法创建纹理很耗时,一张1080p的全屏图需要40ms以上,而android系统自己私有的方法10ms以内就创建完毕了)
- Opengl创建纹理(texture)太耗时,后面使用比系统的速度快,系统被它那套递归绘制等拖累了性能。(opengl来实现ViewPager的效果,android2.2手机除了初始化创建交互的纹理,进行移动的时候8ms左右一帧。)
主动释放控件资源
//释放布局资源
private void releaseDradable(View view){
if (view instanceof ViewGroup) {
int count = ((ViewGroup)view).getChildCount();
for (int i=0;i<count;i++) {
View childView = ((ViewGroup)view).getChildAt(i);
if (childView instanceof ViewGroup) {
releaseDradable(childView);
}else {
releaseOneDrawable(childView);
}
}
}else {
releaseOneDrawable(view);
}
}
//释放常见控件的资源,imageView是多种控件的根类
private void releaseOneDrawable(View view){
if (view !=null) {
BitmapDrawable src;
BitmapDrawable backGround;
if (view instanceof ImageView) {
src = (BitmapDrawable) ((ImageView) view).getDrawable();
recycleBitmap(src.getBitmap());
}
backGround = (BitmapDrawable) view.getBackground();
recycleBitmap(backGround.getBitmap());
}
}
private void recycleBitmap(Bitmap bitmap) {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null; }
}
复制代码
以上思路存在一个问题,即当某一个资源被多个activity引用时,回收该资源则会造成其他持有该资源的activity发生异常。
-
维护一个Drawable链表用以记录引用次数
-
将控件的getDrawable()和getBackground()设置为null
系统Viewpager的性能优化
- V4包里的SwipeRefreshLayout类在接收到down事件的时候,会调用bringToFront方法,该方法会触发requestLayout。
*这里主要是优化ViewPager在添加和删除item的时候,会触发requestLayout导致的卡顿问题。第一次加载是没有优化的,因为必须得触发layout。 * 经过分析,我们的场景不需要这个方法,就去掉了该方法, viewPager的adapter中instantiateItem()会执行container.addView(object),这也会触发requestLayout;destroyItem也会触发requestLayout。可以替换为attachViewToParent和detachViewFromParent方法来进行add和remove。这俩方法是listView中进行动态add和remove的方法,性能很高,不会让缓存失效和触发requestLayout。
我们的ViewPager的setOffscreenPageLimit设置为1。调用detachViewFromParent方法后为了让ViewPager重新录制一下View的绘制,所以又手动调用了invalidate。 录制绘制就是dispatchDraw流程,不然会走getDisplayList流程
在 instantateItem()中调用如下代码
微信db打开方式 用户设备的IMEI+uin值计算MD5值,注意是小写字符,然后在取MD5的前7位字符构成的密码。
关于RelativeLayout的使用 大量使用了RelativeLayout,导致了多次mesure,一个relativelayout都要measure两次,多个层次这种叠加之后,measure次数指数级上升。
关于 SoftwareRefrence 在android低版本上,SoftwareRefrence是遵循java标准的GC回收流程,即只有触发GC的情况为内存不足时,才会去检查SoftReference,但在高版本上,SoftReference被检查的更频繁了,即不是只有内存不足时才去检查,其存在的概率与WeakReference接近。
FragmentTabHost的问题 每次FragmentTabHost切换fragment时会调用onCreateView()重绘UI。 解决方法:
private FragmentTransaction doTabChanged(String tabId,
FragmentTransaction ft) {
TabInfo newTab = null;
for (int i = 0; i < mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
if (tab.tag.equals(tabId)) {
newTab = tab;
}
}
if (newTab == null) {
throw new IllegalStateException("No tab known for tag " + tabId);
}
if (mLastTab != newTab) {
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
if (mLastTab != null) {
if (mLastTab.fragment != null) {
// 将detach替换为hide,隐藏Fragment
// ft.detach(mLastTab.fragment);
ft.hide(mLastTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mContext,
newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
// 将attach替换为show,显示Fragment
// ft.attach(newTab.fragment);
ft.show(newTab.fragment);
}
}
mLastTab = newTab;
}
return ft;
}
复制代码
app启动优化
-
之前优化软件启动时间的时候,就是Application的attachBaseContext()开启method trace,首页的dispatchDraw方法被调用之后关闭,然后这段时间CPU都干了什么。
-
耗时的操作放到了dispatchDraw方法之后post一个回调来执行。
Looper.getMainLooper().setMessageLogging(new Printer() { @Override public void println(String x) { Log.e("msg", x); } }); 复制代码
用这个来测算每个消息花了多长时间,如果消息的执行时间超过了16ms,则获取当前的函数调用栈。 (可以确保你的耗时操作在页面显示之后才执行。)
- 或者还有个onPostResume()方法,我们现在已经不用View的绘制之后再执行操作了,我们改为放到onPostResume方法中执行。(draw方法执行了,页面就显示了,然后不会黑屏或者白屏了)
- app启动流程:Application的构造方法->attachBaseContext()->onCreate()->Activity的构造方法->onCreate()->配置主题中背景灯属性->onStart()...
关于inflate
-
inflate本身是io操作,而手机性能下降很大的一个原因就是io性能变差。
-
动态添加可以解决 xml 加载时间问题;自定义view 可以解决嵌套层级问题。
strings文件下多个同种类型字符串的问题
- 共有%1s单
- ]]>
RecyclerView的item的其他思路
- recyleView中所有类型的item均为继承View,内容完全自己canvas绘制(一个个add进去),View中保存每个item的状态,获得该状态则可绘制出该View,bindView中无xml的inflate,已展示过的item再次显示时无需measure和耗时计算,ViewPager中limited item数为默认1,item被移除时,View内存被释放,再次进入时依靠保存的数据复原原item,此为同步操作。目前看来复原速度很快,用户对其是复原还是缓存的是感知不出来的。
- android的新版本上也measure的结果进行了缓存,文本的测量也使用了100多K的空间进行全局缓存。
今日头条跟手回退实现--群分享记录
-
今日头条也是基于Activity的透明主题来实现的,但是这个方案都有两个缺点,一就是叠加层级一多,滑动性能会下降明显,基本叠加5层就很卡了,二是透明主题破坏了系统内存回收释放的策略,导致所有的activity都是前台Activity,系统都不会回收,就会OOM。解决这个问题有一个方案就是利用android 4.4里提供的动态设置Activity透明主题来实现,当叠加了三层之后就将底部的第三层改为非透明主题。
/** * 动态将一个activity设置为不透明主题 * * @param activity */ public static void convertActivityFromTranslucent(Activity activity) { try { Method method = Activity.class.getDeclaredMethod("convertFromTranslucent"); method.setAccessible(true); method.invoke(activity); } catch (Throwable t) { } } 复制代码
/**
* 动态将一个activity设置为透明主题
*
* @param activity
*/
public static void convertActivityToTranslucent(Activity activity) {
try {
Class<?>[] classes = Activity.class.getDeclaredClasses();
Class<?> translucentConversionListenerClazz = null;
for (Class clazz : classes) {
if (clazz.getSimpleName().contains(
"TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
}
}
if (Build.VERSION.SDK_INT < 21) {//这个也仅支持4.4及以上
Method method = Activity.class.getDeclaredMethod(
"convertToTranslucent",
translucentConversionListenerClazz);
method.setAccessible(true);
method.invoke(activity, new Object[]{null});
} else {//5.0以上的系统
Method method = Activity.class.getDeclaredMethod(
"convertToTranslucent",
translucentConversionListenerClazz,
ActivityOptions.class);
method.setAccessible(true);
method.invoke(activity, null, null);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
复制代码
-
反编译了QQ空间的apk,他们把每个类的构造函数中添加了一行Zygote.class.getName(),Zygote是系统中存在的一个类。他们选用Zygote是android SDK中不存在的,被隐藏的类,他们应该是认为以后每个版本系统中都会有这个类,所以才选择了它。那么系统进行检查的时候就不会认为初始化该类只需要使用当前Dex。(防止CLASS_ISPREVERIFIED )
-
animation有个onAnimationStart和onAnmationEnd方法里面不可使用addView/removeView的方法,有可用handler.postRunable()来执行;开启硬件加速的时候有一些手机上会有概率性问题。硬件加速中draw流程只是进行录制,如果在录制的之后进行绘制的时候发现之前录制的已经无效了,在4.X的机型上就可能发生崩溃。动画的回调是在draw流程中执行的,在回调进行动态的removeView就会导致录制的绘制命令无效。
-
线程的使用
ArrayList <File> list = new ArrayList<>();
public void scanDir(String dirString){
File dirFile = new File(dirString);
list.add(dirFile);
while (list.size() > 0){
File files[] = list.remove(0).listFiles();
for(File file : files){
if(file.isDirectory()){
list.add(file);
}else{
if(file.getAbsolutePath().endsWith("mp3")){
Log.e("", "这是音乐文件");
}
}
}
}
}
复制代码
把递归变成队列,再把队列变成多线程执行,下面只需要开启多个线程来执行scanDir(),比如一共有n+1(CPU核心数+1)个线程来执行scanDir(),当list有第一个元素时开启一个线程执行scanDir(),这个线程会往list中继续添加元素,当开启的线程小于n+1时,继续开启线程,直到达到n+1,达到之后就等待线程执行完毕,其中某个线程执行完毕之后再次去list中获取底部的元素来执行scanDir,直到list大小为0。
- LayoutParams 的问题 直接new出来 View 如果不设置LayoutParams 就 add进一个viewgroup类型 它的LayoutParams 是由 父viewgroup generateDefaultLayoutParams函数 决定的。
- 类型强转的注意点 强转前加 if xxx instanceof xx的校验,典型问题: 兼容包下的控件getContext不能强转为Activity(布局文件写入控件,activity继承AppCompatActivity ) 追溯View第二个构造函数发现context是LayoutInfalte传来的,发现是LayoutInflater.from传来的,发是phoneWindow传来的 发现newPhoneWindow(Activity) ,这样按理说view的context本身就是activity,可是报错说是 tintContextWrapper
- activity在做动画的时候,页面的绘制是暂停的,或者只是绘制几帧。调用一个方法可以让其在动画过程中不暂停绘制。 反射调用
ViewRootImpl
中的setDrawDuringWindowsAnimating(true) 在
onAttachedToWindow后`调用(api 19及以上)类似的可以有:
private boolean sInited = false;
private Method msetDrawDuringWindowsAnimatingMethod;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT< 24) {
Object viewRootImpl = ((Activity)getContext()).getWindow().getDecorView().getParent();
if (!sInited) {
try {
msetDrawDuringWindowsAnimatingMethod = viewRootImpl.getClass().getMethod("setDrawDuringWindowsAnimating", boolean.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
if (msetDrawDuringWindowsAnimatingMethod != null) {
try {
msetDrawDuringWindowsAnimatingMethod.invoke(viewRootImpl, true);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
sInited = true;
}
}
}
复制代码
- 优化View的inflate 如果你发现创建某个View的inflate很耗时,或者是measure和layout很耗时,你就只有采用类似这样的方式来优化它
耗时的工作完成的,把addViewInLayout之类的post到主线程就行了。 只有被添加到View树上的时候才可能会绘制,刚inflate出来的View没有被添加到View树上,所以不会进行measure和layout。measure是耗时的,如果在线程中执行,它就会减少主线程的卡顿,最后再添加到View树上,并且不用触发requestLayout。相当于异步加载view 使用场景:比如进入一个页面开启网络加载一段内容,当网络数据回来了就刷新页面,如果这个页面是列表,那么如果是正在滑动的时候刷新页面就会卡顿,这时就可以使用这种方式来进行优化。
- 利用aapt解析apk信息
aapt dump badging demo.apk |grep version
aapt的其他参数,也比较使用,比如向apl中插入文件,删除文件,这个和unzip的效果是一样一样的。过去没用appt时,我们修改apk信息经常用zip/unzip,现在用appt也可以搞定,还能避免有的系统没有安装zip/unzip的问题
aapt a demo.apk test.txt
-
Activity被销毁分为两种:1. Activity对象被从ActivityThread中移除了,这时只是把java对象置null,如果你其他地方还持有该对象,这个activity是不会被释放的。 2. Activity所在的进程被回收,那它所有的资源都被回收了。只会有一些可序列化的数据被保存。
-
以下条件webview可以关闭硬件加速
//mModelNumber = Build.MODEL;
复制代码
- 获取GPU刷新帧率
可以利用Looper的log机制和添加全局控件来自己实现那个柱状图。
-
列表优化思路 1.item中不能使用任何xml,包括xml的drawable;2.减少View个数层级降低过度绘制;3.自己实现TextView,系统的TextView(特别是android 7.0以下系统)性能太烂。
-
anr adb shell ls /data/anr/
adb pull /data/anr/traces.txt ~/Desktop
- Handler中一段泄漏风险检测的代码
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
复制代码
该思想可用于多种自测内存泄漏的场景。
- 使用递归时,留意函数栈是否会爆
- 线程池的大小经验值设置:(其中N为CPU的个数)
如果是CPU密集型应用,则线程池大小设置为N+1, 如果是IO密集型应用,则线程池大小设置为2N+1
如果一台服务器上只部署这一个应用并且只有这一个线程池,那么这种估算或许合理,具体还需自行测试验证。但是,IO优化中,这样的估算公式可能更适合:最佳线程数目 =((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目因为很显然,线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。下面举个例子:比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到: ((0.5+1.5)/0.5)*8=32
。这个公式进一步转化为:最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目