基本的优化总结(七)

导言

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

内存泄漏

说了那么久的内存泄漏,实际上就是对象超过了它本来应该存在的生命周期,导致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? = 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惩罚模式也有打印日志和直接奔溃等几种行为,推荐通过打印日志的方式来具体定位问题并且进行修改

结论

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

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

你可能感兴趣的:(基本的优化总结(七))