Android性能优化详解

        Android设备作为一种移动设备,无论是内存还是CPU的性能都受到了很大的限制,这导致Android程序的性能问题异常突出,对于性能优化提出了更高的要求。本文就跟大家一起探索一下我们日常开发APP有什么可以注意的点,让我们的APP可以拥有更流畅,更好的用户体验.

     1.布局优化

  • 布局复用,使用标签重用layout;
  • 提高显示速度,使用延迟View加载,有一些比如错误页面不一定要显示的就可以使用这个,有需要的时候在加载出来;
  • 减少层级,使用标签替换父级布局;
  • 注意使用wrap_content,会增加measure计算成本;
  • 删除控件中无用属性;
  • 注意使用合理的控件,当一样层级可以实现的时候效率FrameLayout>LinearLayout>RelativeLayout,当然如果使用RelativeLayout可以减少层级嵌套那也是值得的.尽量使用ConstraintLayout这个约束布局,基本可以保证层级只有一层
  • 减少重绘,移除window的背景,减少布局中不必要的背景设置
  • Android性能优化详解_第1张图片

   对于检查布局层级,Android提供了一个很好用的工具HierarchyViewer,位于Android SDK/tools/hierarchyviewer.bat 可以很方便的看我们布局的层级结构

2.内存优化

内存优化应该是Android优化中最重要的点了,我们着重讲这块

讲内存优化之前我们先来简单的看看JVM中的垃圾回收机制:

我们知道JVM的内存模型主要分为五块

Android性能优化详解_第2张图片

        我们一般说的内存优化一般是优化堆内存,JVM特有的垃圾回收机制GC,当堆内存不足的时候就会触发垃圾回收,将无用对象给回收掉,执行GC操作的时候,所有线程的任何操作都会需要暂停,等待GC操作完成之 后,其他操作才能够继续运行。内存泄漏 会导致应用剩余可用Heap Size越来越小,这样会导致频繁触发GC。通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染)(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了,从而导致丢帧,用户也会发现应用卡顿。

   常见的内存泄露:

  (1)单例造成的内存泄漏

public class SingleInstance {
    private Context mContext;
    private static SingleInstance instance;
 
    private SingleInstance(Context context) {
        this.mContext = context;
    }
 
    public static SingleInstance getInstance(Context context) {
        if (instance == null) {
            instance = new SingleInstance(context);
        }
        return instance;
    }
 
    public void say() {
        Log.i("tag", "this is single instance");
        Log.i("tag", ":code:" + instance.hashCode());
    }
}

这是一个很普通的单例模式,但是这会造成内存泄露,如果在一个Activity中直接调用SingInstance.getInstance(this)就会造成内存泄露,这是因为生命周期长的对象引用了生命周期短的对象,导致这个activity的对象无法被回收,我们应该改为使用全局的上下文来获取单例对象

(2)Handler造成的内存泄漏     

Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            
            return false;
        }
    });

直接在activity中使用Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,消息队列是在一 个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏

应该改为:

private  MyHandler myHandler = new MyHandler(this);
    class MyHandler extends Handler{
        private WeakReference reference;

        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity context = (MainActivity) reference.get();
            if (context !=null){
                ....
            }
        }
    }

    @Override
    protected void onDestroy() {
        myHandler.removeCallbacksAndMessages(null);
    }

(3)线程造成的内存泄漏     

new AsyncTask(){
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(1000);
                return null;
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(1000);
            }
        });

这类会因为activity销毁的时候定时任务还在执行会造成内存泄露,解决方法就是在Activity销毁时候也应该取消相应的任务,避免任务在后台执行浪费资源;采用rxJava处理异步事件,然后将rxJava的生命周期与Activity的生命周期进行绑定

(4)资源未关闭造成的内存泄漏   

  对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏,应该在activity销毁的时候及时回收资源

(5注册了系统的服务,但onDestory未注销     

一些广播,比如EventBus,BroadcastReceiver,传感器之类的,注册的时候记得在onDestory的时候解除注册.

对于内存泄露我们主要有这些工具可以排查:

  •   android studio自带的Moniters
  •  项目引入leakcanary
  • 利用mat(Memory Analysis Tools)工具分析

内存优化另一个重要点的是OOM即(Out Of Memoery),顾名思义就是指内存溢出了内存溢出是指APP向系统申请超过最大阀值的内存请求,系统不会再分配多余的空间,就会造成OOM error。在我们Android平台下,多数情况是出现在图片不当处理加载的时候.

有人说Android的APP所能申请到的内存是16M有的说是32M,其实这都是不一定的,Java为我们提供了单例获取的方式Runtime.getRuntime()就可以获取到内存空间的大小

我们知道图片的所占内存计算: 内存 = 图片长度 * 图片宽度 * 每个像素所占的内存

1. ALPHA_8:每个像素点占用1byte内存 
2. ARGB_4444:每个像素点占用2byte内存 
3. ARGB_8888: 每个像素点占用4byte内存 
4. RGB_565: 每个像素点占用2byte内存

如果一张3000*3000的ARGB_8888的图片所占的内存即:3000*3000*4/1024/1024 ≈ 34.33M,这个内存就很有可能会造成内存溢出了,而一张3000*3000的RGB_565的图片所占的内存即:3000*3000*2/1024/1024 ≈ 17.16M,所以相同的情况下如果对图片的要求不是特别高的话建议使用RGB_565,效果差别不是很明显,能有效减少内存.

加载本地图片的时候使用流的方式加载会比较省内存

public static Bitmap readBitMap(Context context, int resId) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        opt.inPurgeable = true;
        opt.inInputShareable = true;
        //获取资源图片
        InputStream is = context.getResources().openRawResource(resId);
        return BitmapFactory.decodeStream(is, null, opt);
    }

对于网络图片还不知道尺寸大小的可以先采用BitmapFactory.Options的inJustDecodeBounds属性来获取图片的属性,然后在根据控件的大小对图片进行压缩到我们所需要的大小.最后在加载网络图片的时候最好是采用缓存,常见的内存缓存LruCache,磁盘缓存DisLruCache,底层都是基于LinkedHashMap,LinkedHashMap采用的双向链表实现近期最少使用的算法,当内存满的时候优先删除近期少使用的.现在市面上的第三方加载图片的框架大多都是基于此算法

关于图片的优化还有一个比较重要的点,那就是加载巨图长图的时候,又不能压缩,要能滑动观看全部,这个时候怎么办呢?

这个时候就要用到:BitmapRegionDecoder这个类了

这个类可以根据设置的区域加载图片,实现按照所需的范围加载图片

BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);

关于内存优化还有几个要注意的点:

  • 自定义View的时候如果有重叠的就使用canvas.clipRect显示绘制区域,只显示看得到的那部分
  • 自定义View的时候不要在onDrow执行耗时操作,不要重复创建大量对象,因为onDrow方法会多次执行
  • 使用ListView,RecyclerView的时候注意复用,滑动的时候停止图片的加载已到达流畅滑动的效果

3.APK体积优化

(1) 使用Lint清理无用资源

Android性能优化详解_第3张图片

(2) 包体分析

Android性能优化详解_第4张图片

(3)开启Gradle去除无用资源

Android性能优化详解_第5张图片

(4)使用tinypng有损压缩

Android性能优化详解_第6张图片

(5)使用微信资源压缩打包工具

Android性能优化详解_第7张图片

(6)

Android性能优化详解_第8张图片

(7) FaceBook的redex优化字节码方案

Android性能优化详解_第9张图片

好了,今天就讲到这里了,希望对大家有所帮助.

你可能感兴趣的:(Android)