基本的优化总结(七)

导言

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

内存泄漏

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

结论

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342