美文网首页
基本的优化总结(七)

基本的优化总结(七)

作者: 范锦浩 | 来源:发表于2018-06-25 17:59 被阅读28次

    导言

    上一节主要讲了分析内存问题的一些工具,这一节主要是总结一些常见的场景

    内存泄漏

    说了那么久的内存泄漏,实际上就是对象超过了它本来应该存在的生命周期,导致GC没有成功回收它,在Android中,主要就是Activity,因为Activity有自己的生命周期并且一些页面所占用的内存相当的大,所以说如果在退出之后没有被正常回收,这部分内存泄露还是比较厉害的

    静态变量导致的泄露

    这个很简单,都知道static修饰的变量会存于方法区中,并且在整个进程运行中不会被GC,所以说一旦static持有了context,那么必须注意泄露的问题
    使用static的主要场景就是单例和复用,看一个例子

    class LeakSinglton{
        companion object {
            @Volatile private var INSTANCE:LeakSinglton? = null
            fun getInstance(context:Context):LeakSinglton?{
                if(null == INSTANCE){
                    synchronized(LeakSinglton::class){
                        if(null == INSTANCE){
                            INSTANCE = indi.fanjh.usekotlin.LeakSinglton(context)
                        }
                    }
                }
                return INSTANCE
            }
        }
    
        var mContext:Context? = null
    
        private constructor(context:Context){
            mContext = context
        }
    
    }
    

    其实如果必须要使用单例的话,有一个非常简单的解决方法,那就是使用ApplicationContext,不过此时启动Activity等功能可能会受限,所以说要考虑清楚这个单例是否必要再具体使用,不能只图功能实现

        private constructor(context:Context){
            mContext = context.applicationContext
        }
    
    异步任务导致的泄露等问题

    比方说网络请求和一些耗时的操作,常规的处理都是在一个工作线程中处理,然后等待处理完毕再投递回主线程中处理
    这会导致两个问题:
    1.异步回调的空指针问题,如果你在Activity的onDestroy中手动释放了资源,那么要求你在回调中必须处理
    2.内存泄露,当前Activity在执行中已经退出,但是也只能等待耗时任务完成之后,后续的GC才有可能回收该Activity的资源
    这类问题的常见解决方案:
    1.Handler相关,需要考虑的就是大部分异步任务是通过Handler最后回调会主线程,并且Handler本身也可能执行延时任务

        class WeakHandler: Handler {
            var weakRef:WeakReference<TestMeasureActivity>? = null
    
            constructor(testMeasureActivity: TestMeasureActivity){
                weakRef = WeakReference(testMeasureActivity)
            }
            
            override fun handleMessage(msg: Message?) {
                super.handleMessage(msg)
                val activity = weakRef!!.get()
                if(null != activity){
                    //说明当前Activity还不应该被回收,可以继续操作,do something
                }
            }
        }
    

    回调方面的处理就是通过弱引用来持有Activity,因为弱引用持有的对象在只有弱引用持有的情况下,GC也是会进行回收的,此时get()获得的就是null对象

        override fun onDestroy() {
            super.onDestroy()
            handler.removeCallbacksAndMessages(null)
        }
    

    在对应的Activity的onDestroy中移除handler中所有未执行的回调和消息,这样也可以有效避免延时任务的再执行
    2.Thread类型,这个和Handler类似,需要注意的就是后台任务需要在合适的时机进行终止,避免其肆意执行
    比方说AsyncTask

        override fun onDestroy() {
            super.onDestroy()
            asyncTask!!.cancel(true)
        }
    

    实际上就是在onDestroy中终止,并且尝试中断当前线程
    对于普通的线程来说,也是一样的处理,比方说

    class ThreadDemoActivity: Activity(){
        companion object {
            const val TAG = "ThreadDemoActivity"
        }
        var mThread:CalculateThread? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            mThread = CalculateThread()
            mThread!!.start()
        }
    
        override fun onDestroy() {
            super.onDestroy()
            if(mThread != null){
                mThread!!.isCancelled = true
                //如果你认为应该强势中断线程,那么你可以
                var isSuccess = false
                try {
                    mThread!!.interrupt()
                    isSuccess = true
                }finally {
                    Log.d(TAG,"interrupt_result:$isSuccess")
                }
            }
        }
    
        class CalculateThread:Thread(){
            var isCancelled = false
    
            override fun run() {
                super.run()
                //耗时操作一
                if(isCancelled){
                    return
                }
                //耗时操作二
                if(isCancelled){
                    return
                }
                //后续普通操作
            }
    
        }
    
    }
    

    换言之,线程有自然终止和强行中断两种操作,这里要根据具体的场景自行选择使用

    注册和反注册问题

    这个实际上也是长久持有的问题,比方说EventBus就是static持有,常见于观察者模式下的单例,广播是因为系统服务长期存在的原因,总之就是注意注册了要反注册,要成对存在

    资源释放

    这个指的就是一些持有大量资源的类的释放,比方说IO流和Cursor,这两个最常见

    图片处理

    这里实际上就是说的Bitmap的处理,从上一节当中也可以看到,实际上图片内存占用是非常大,所以说合适的图片处理也是很重要的

    图片选择问题

    考虑图片加载的使用场景,比方说同样是华为手机,但是一个屏幕密度高,一个屏幕密度低,此时实际上展示在手机上面的同一幅图像素实际上是不一样的,此时简单的方案是直接使用一张大图,然后通过图片加载库进行压缩等处理展示
    比方说480x480,处理为160x160和240x240两种,但是很明显的,最合适的做法应该是根据屏幕密度来下发具体的图片,我们知道网络图片的加载地址来源于服务端,那么如果我们在请求接口的时候带上当前要请求的图片密度,然后区别返回xxx/v1/demo.jpg或者xxx/v2/demo.jpg,并且图片的大小和实际展示的像素大小一致,这样是最好的选择
    上述相对于一个源图片来说,实际上就是解决了两个问题
    1.不同密度的图片本身大小就不同,那么低密度的手机在进行网络请求请求图片的时候,这个过程消耗的流量会少一些,并且加载的速度也会快一些
    2.图片的尺寸完全匹配,那么将会减少多余的压缩等处理,也会加快图片的展示速度

    图片质量问题

    先看图片格式选择
    现在市场上常用的图片有jpg、png以及webp,webp的介绍可以看下面这个链接
    https://zhuanlan.zhihu.com/p/23648251
    下面稍微介绍一下三者的区别:
    jpg:有损压缩,并且没有Alpha通道,相对于png来说会小一点,非常适合一些没有Alpha通道的大图
    png:无损压缩,支持Alpha通道,一般在追求图片质量的时候可以考虑,适用于一些小图
    webp:Google出品,支持有损和无损压缩两种模式,并且图片大小会小于png和jpg,缺点就是Android原生在4.0之后才支持,并且透明的在4.2.1之后才会完美支持,并且webp动图目前只有Fresco支持

    图片像素质量
    每一个像素点需要存储在byte[]中,那么每一个像素点的存储方式也会影响到图片的大小

        /**
         * Possible bitmap configurations. A bitmap configuration describes
         * how pixels are stored. This affects the quality (color depth) as
         * well as the ability to display transparent/translucent colors.
         */
        public enum Config {
            // these native values must match up with the enum in SkBitmap.h
    
            /**
             * Each pixel is stored as a single translucency (alpha) channel.
             * This is very useful to efficiently store masks for instance.
             * No color information is stored.
             * With this configuration, each pixel requires 1 byte of memory.
             */
            ALPHA_8     (1),
    
            /**
             * Each pixel is stored on 2 bytes and only the RGB channels are
             * encoded: red is stored with 5 bits of precision (32 possible
             * values), green is stored with 6 bits of precision (64 possible
             * values) and blue is stored with 5 bits of precision.
             *
             * This configuration can produce slight visual artifacts depending
             * on the configuration of the source. For instance, without
             * dithering, the result might show a greenish tint. To get better
             * results dithering should be applied.
             *
             * This configuration may be useful when using opaque bitmaps
             * that do not require high color fidelity.
             */
            RGB_565     (3),
    
            /**
             * Each pixel is stored on 2 bytes. The three RGB color channels
             * and the alpha channel (translucency) are stored with a 4 bits
             * precision (16 possible values.)
             *
             * This configuration is mostly useful if the application needs
             * to store translucency information but also needs to save
             * memory.
             *
             * It is recommended to use {@link #ARGB_8888} instead of this
             * configuration.
             *
             * Note: as of {@link android.os.Build.VERSION_CODES#KITKAT},
             * any bitmap created with this configuration will be created
             * using {@link #ARGB_8888} instead.
             *
             * @deprecated Because of the poor quality of this configuration,
             *             it is advised to use {@link #ARGB_8888} instead.
             */
            @Deprecated
            ARGB_4444   (4),
    
            /**
             * Each pixel is stored on 4 bytes. Each channel (RGB and alpha
             * for translucency) is stored with 8 bits of precision (256
             * possible values.)
             *
             * This configuration is very flexible and offers the best
             * quality. It should be used whenever possible.
             */
            ARGB_8888   (5),
    
            /**
             * Each pixels is stored on 8 bytes. Each channel (RGB and alpha
             * for translucency) is stored as a
             * {@link android.util.Half half-precision floating point value}.
             *
             * This configuration is particularly suited for wide-gamut and
             * HDR content.
             */
            RGBA_F16    (6),
    
            /**
             * Special configuration, when bitmap is stored only in graphic memory.
             * Bitmaps in this configuration are always immutable.
             *
             * It is optimal for cases, when the only operation with the bitmap is to draw it on a
             * screen.
             */
            HARDWARE    (7);
    
            final int nativeInt;
    
            private static Config sConfigs[] = {
                null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
            };
    
            Config(int ni) {
                this.nativeInt = ni;
            }
    
            static Config nativeToConfig(int ni) {
                return sConfigs[ni];
            }
        }
    

    实际上关注RGB_565和ARGB_8888即可,在没有Alpha通道的时候,使用RGB_565对于内存来说是最好的选择,对比于ARGB_8888来说,每一个像素点可以接受2个字节的大小,对于一张图片来说60 * 60 * 2都可以节省下7KB左右的大小,在网络图片为jpg的时候这个为最佳选择。

    对象分配

    个人总结其实就几个方面:
    1.尽量懒加载,特别是static变量,使用的地方再初始化
    2.POJO中的属性,boolean或者int类型尽量不要用String修饰,这样会导致在堆中分配很多多余对象,int则可以做到尽可能的复用栈中数据
    3.String类型操作,如果有大量拼接操作请使用StringBuilder,实际上+号拼接多个对象的时候也是new StringBuilder实现,所以说特别在for循环当中,能够在外部定义一个StringBuilder对象处理最优

    StrictMode

    Android提供了工具进行上述的一些方面的自动检查,使用也很简单,比方说在Application的onCreate中处理

    class MainApplication:Application(){
        override fun onCreate() {
            StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build())
            StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build())
            super.onCreate()
    }
    

    StrictMode主要可以作以下方面的检查:
    1.IO读写操作是否在工作线程中进行
    2.网络请求是否在工作线程中进行
    3.自定义标记线程,可以进行慢线程操作的检查
    4.Closeable对象是否close
    5.SQLiteDatabase及Cursor是否close
    6.Activity泄露
    7.BroadcastReceiver及ServiceConnection导致的Context泄露,主要就是注册后是否反注册,ServiceConnection因为bindService是通过Context作为key缓存的,所以说也要注意unBindService
    。。。
    上述的这些检查项目是允许开发者自己选择的,从我个人的角度来说,建议全部开启,毕竟养成良好的编程习惯要从平时做起
    检查到问题之后,StrictMode惩罚模式也有打印日志和直接奔溃等几种行为,推荐通过打印日志的方式来具体定位问题并且进行修改

    结论

    这一节主要是一些经验之谈,关于内存方面的,这一块需要通过大量的项目实践来慢慢感受,但是有一点是确认的,良好的编程习惯应该从平时养成,开发者应该对自己的代码有这比较高的要求才行

    文章系列:
    基本的优化总结(一)
    基本的优化总结(二)
    基本的优化总结(三)
    基本的优化总结(四)
    基本的优化总结(五)
    基本的优化总结(六)
    基本的优化总结(七)

    相关文章

      网友评论

          本文标题:基本的优化总结(七)

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