Android面试之性能优化篇

面试专题我放在git上了,地址Github 欢迎fork然后一起更新

1,什么是anr?

应用程序无响应,主线程做了耗时操作导致的可以通过查看/data/anr/traces.txt查看ANR信息。
根本原因是:主线程被卡了,导致应用在5秒时间未响应用户的输入事件
ANR有三种类型:
1,keyDispatchTimeOut(5秒),按键或者触摸事件在特定事件内无响应
2,BroadcastTimeOut(10秒),BroadcastReceiver 在特定事件内无法处理完成
3,ServiceTimeOut(20秒) ,超时的原因一般有2种:当前事件没有机会得到处理,当前正在处理却没有及时完成。

2,anr的主要原因

主线程做了IO操作导致阻塞,从4.0后网络IO不允许在主线程中,主线程中存在耗时的计算
主线程:
Activity的所有生命周期回调,
Service默认是主线程,(IntentService除外)
BroadcastReeiver的onReceive的回调,
post(Runnable)也是执行在主线程,
AsyncTask的回调中除了doInbackground外,其他的都是主线程
很多种ANR错误出现的场景:
1) 主线程当中执行IO/网络操作,容易阻塞。
2) 主线程当中执行了耗时的计算。----自定义控件的时候onDraw方法里面经常这么做。
(同时聊一聊自定义控件的性能优化:在onDraw里面创建对象容易导致内存抖动---绘制动作会大量不断调用,产生大量垃圾对象导致GC很频繁就造成了内存抖动。)内存抖动就容易造成UI出现掉帧卡顿的问题
3) BroadCastReceiver没有在10秒内完成处理。
4) BroadCastReceiver的onReceived代码中也要尽量减少耗时的操作,建议使用IntentService处理。
5) Service执行了耗时的操作,因为service也是在主线程当中执行的,所以耗时操作应该在service里面开启子线程来做。
6) 使用AsyncTask处理耗时的IO等操作。
7) 使用Thread或者HandlerThread时,使用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)或者java.lang.Thread.setPriority (int priority)设置优先级为后台优先级,这样可以让其他的多线程并发消耗CPU的时间会减少,有利于主线程的处理。
8) Activity的onCreate和onResume回调中尽量耗时的操作。

3,如何解决anr

使用AsyncTask处理耗时IO操作
使用Thread或者HandlerThread提高优先级
使用Handler来处理工作现场的耗时任务

4,什么情况导致OOM

当前占用的内存加上我们申请的内存资源超过Dalvik虚拟机的最大内存限制就会抛出Out of memory异常。
OOM产生的原因:内存不足,android系统为每一个应用程序都设置了一个硬性的条件:DalvikHeapSize最大阀值64M/48M/24M.如果你的应用程序内存占用接近这个阀值,此时如果再尝试内存分配的时候就会造成OOM。
1)内存泄露多了就容易导致OOM
2)大图的处理。压缩图片。平时开发就要注意对象的频繁创建和回收。
3)可以适当的检测:ActivityManager.getMemoryClass()可以用来查询当前应用的HeapSize阀值。可以通过命名adb shellgetProp | grep dalvik.vm.heapxxxlimit查看。

如何解决OOM?
减小对象的内存占用:
使用轻量级的数据结构,替代hashmap,用sparseArray,Hashmap会导致一些没必要的自动装箱和拆箱。
适当的避免在android中用美剧,替代使用普通的static常量
内存对象的重复利用:
使用对象池技术,1自己写,2利用系统既有的对象池机制。比如LRU算法(LastRecently Use)
关于Bitmap:
图片显示,滑动不加载,停止加载,
根据手机分辨率缩小对应图片的大小,用inSimpleSize,不用加载内存就可以获取bitmap的宽高大小
图片解码格式选择:ARGB_8888/RGB_565/ARGB_4444/ALPHA_8,还可以使用webp
及时释放内存recycle;
捕获异常
其他方法:
listview,convertview,大图用Lru,三级缓存,最近最小使用缓存对象
避免在onDraw方法里面执行对象的创建
谨慎使用多进程

5,Bitmap

recycle:对bitmap的内存的回收,方法不可逆;官网不建议调用,说系统会走垃圾回收机制,不过可以自己根据需求来调用

LRU:LruCache,内部用一个LinkedHashMap,
提供get,put 方法来完成缓存的添加和获取操作,
当缓存满了之后,Lru内部一个trimToSize方法会移除较早缓存对象,
然后把新的缓存对象添加到缓存中;

计算inSampleSize

public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;
    if (height > reqHeight || width > reqWidth){
        if (width > height){
            inSampleSize = Math.round((float)height /(float)reqHeight);
        }else {
            inSampleSize = Math.round((float)width /(float)reqWidth);
        }
    }
    return inSampleSize;
}

缩略图
通过inJustDecodeBounds = true来设置加载缩略图进内存

三级缓存,网络,本地,内存,
首次从网络获取,保存到sd卡和内存相应的各一份,如果再次获取,那就从内存,sd卡,网络以此找缓存

6,UI卡顿

卡顿原理:60fps -> 16ms内渲染
overdraw:

原因:
1,人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
2,布局Layout过于复杂,无法在16ms内完成渲染
3,同一时间动画执行的次数过多,导致CPU或GPU负载过重
4,View过渡绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重
5,View频繁的触发measure,layout,导致measure,layout累积耗时过多及整个View频繁的重新渲染
6,内存频繁触发GC过多,导致暂时阻塞渲染操作
7,冗余资源以及逻辑等导致加载和执行缓慢
8,ANR

优化总结:
1,布局优化,不要冗余嵌套,include导入,merge标签,如果足够复杂就用自定义view来处理
2,列表滑动的时候不要加载图片,滑动的时候显示缩略图或者默认图
3,图片和背景内存分配,减少背景压缩图片大小
4,避免ANR

7,内存泄露,什么情况导致?

内存泄露就是指该被GC垃圾回收的,但被一个生命周期比它长的对象仍然在引用它,导致无法回收,造成内存泄露,过多的内存泄露会导致OOM。
1)非静态内部类、匿名内部类:非静态内部类、匿名内部类 都会持有外部类的一个引用,如果有一个静态变量引用了非静态内部类或者匿名内部类,导致非静态内部类或者匿名内部类的生命周期比外部类(Activity)长,就会导致外部类在该被回收的时候,无法被回收掉,引起内存泄露, 除非外部类被卸载。
解决办法:将非静态内部类、匿名内部类 改成静态内部类,或者直接抽离成一个外部类。 如果在静态内部类中,需要引用外部类对象,那么可以将这个引用封装在一个WeakReference中。

2)静态的View:当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。但View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。
解决办法: 在使用静态View时,需要确保在资源回收时,将静态View detach掉。

3)Handler:在Activity中定义Handler对象,那么Handler持有Activty的引用。而每个Message对象是持有Handler的引用的(Message对象的target属性持有Handler引用),从而导致Message间接引用到了Activity。如果在Activty destroy之后,消息队列中还有Message对象,Activty是不会被回收的。
解决办法: 将Handler放入单独的类或者将Handler放入到静态内部类中(静态内部类不会持有外部类的引用)。如果想要在handler内部去调用所在的外部类Activity,可以在handler内部使用弱引用的方式指向所在Activity,在onDestory时,调用相应的方法移除回调和删除消息。

4)监听器(各种需要注册的Listener,Watcher等):当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。
解决办法:在onDestory中移除注册

5). 资源对象没关闭造成内存泄漏:当我们打开资源时,一般都会使用缓存。比如读写文件资源、打开数据库资源、使用Bitmap资源等等。当我们不再使用时,应该关闭它们,使得缓存内存区域及时回收。
解决办法:使用try finally结合,在try块中打开资源,在finally中关闭资源

6). 属性动画:在使用ValueAnimator或者ObjectAnimator时,如果没有及时做cancel取消动画,就可能造成内存泄露。因为在cancel方法里,最后调用了endAnimation(); ,在endAnimation里,有个AnimationHandler的单例,会持有属性动画对象的引用。
解决办法:在onDestory中调用动画的cancel方法

7). RxJava:在使用RxJava时,如果在发布了一个订阅后,由于没有及时取消,导致Activity/Fragment无法销毁,导致的内存泄露。
解决办法:使用RxLifeCycle

8,Android内存泄露

单例(如果使用context容易泄露,应该使用getApplicationContext)
匿名内部类
避免使用static变量
资源未关闭造成的内存泄露
AsyncTask造成的内存泄露

举个例子,正确的handler在activity中的使用方法,如下方法可以避免handler内存泄露:

private Myhandler mHandler = new Myhandler(this);
private TextView mTextView;
private static class Myhandler extends Handler{
    private WeakReference reference;
    public Myhandler(Context context){
        reference = new WeakReference<>(context);
    }
    @Override
    public void handleMessage(Message msg) {
        GuideActivity activity = (GuideActivity)reference.get();
        if (activity!= null){
            activity.mTextView.setText("");
        }
    }
}
private void send(){
    Message message = Message.obtain();
    mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}

9,内存泄露和内存溢出的区别?

(1)内存泄漏
1)内存泄漏:指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统奔溃等严重后果。
2)一次内存泄漏似乎不会有大的影响,但内存泄漏后堆积的结果就是内存溢出。
3)内存泄漏具有隐蔽性,积累性的特征,比其他内存非法访问错误更难检测。这是因为内存泄漏产生的原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏不会直接产生可观察的错误,而是逐渐积累,降低系统的整体性性能。
4)如何有效的进行内存分配和释放,防止内存泄漏,是软件开发人员的关键问题,比如一个服务器应用软件要长时间服务多个客户端,若存在内存泄漏,则会逐渐堆积,导致一系列严重后果。
(2)内存溢出
指程序在申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,就会导致内存不够用,报错OOM,即出现内存溢出的错误。

10,内存泄露有什么检测方法?

检测:
1,DDMS Heap 发现内存泄露: dataObject totalsize的大小,是否稳定在一个范围内,如果操作程序,不断增加,说明内存泄露
2,使用Heap Tool 进行内存快照前后对比,BlankActivity 手动触发GC进行前后对比,对象是否被及时回收
定位:
1,MAT插件打开hprof具体定位内存泄露:查看histogram项,选中某一个对象,查看它的GC引用链,因为存在GC引用链的,说明无法回收
2,AS的Allocation Tracker:观测期间的内存分配,那些对象呗创建,什么时候创建,从而准确定位。

11,内存管理

1,内存管理机制
分配机制
内存回收机制
2,Android的内存管理机制
系统会给每个app都分配一定数量的内存
3,内存优化方法
当Service完成任务后,尽量停止它。

12,冷启动优化

1,冷启动就是在启动应用前,系统中没有该应用的任何进程信息
减少onCreate 方法的工作量
不要让Application参与业务的操作
不要在Application进行耗时操作
不要以静态变量的方式在Application中保存数据
减少布局深度,viewstub

13,其他优化

android不用静态变量存储数据
静态变量等数据由于进程已经被杀死而被初始化
使用其他数据传输,sp

Sharepreference的问题-- 不能跨进程同步,存储的文件过大会造成严重的问题

14,缩小APK包的大小

使用Proguard混淆代码,不使用重复或不用的代码,谨慎添加libs、大多数情况下只需要支持armabi和x86的架构就可以了
使用Lint工具查找没有使用的自然,然后去除不使用的所有资源文件,图片等
使用tinypng对图片压缩预处理,多使用9.png,拉伸区域切小
图片使用xhdpi,更具需要提供差异其他部分即可
重用已有的图片资源,对称的图片只需要一张,用代码来控制旋转
能用代码实现绘制的功能,就不要用图片,比如button的背景

你可能感兴趣的:(Android面试之性能优化篇)