Android面试之性能优化篇

作者: hloong | 来源:发表于2020-05-21 08:25 被阅读0次

    面试专题我放在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<Context> 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面试之性能优化篇

        本文链接:https://www.haomeiwen.com/subject/txtoohtx.html