基本的優(yōu)化總結(jié)(七)

導(dǎo)言

上一節(jié)主要講了分析內(nèi)存問題的一些工具,這一節(jié)主要是總結(jié)一些常見的場(chǎng)景

內(nèi)存泄漏

說了那么久的內(nèi)存泄漏哲泊,實(shí)際上就是對(duì)象超過了它本來應(yīng)該存在的生命周期笆豁,導(dǎo)致GC沒有成功回收它票髓,在Android中珍策,主要就是Activity,因?yàn)锳ctivity有自己的生命周期并且一些頁面所占用的內(nèi)存相當(dāng)?shù)拇罂越疲哉f如果在退出之后沒有被正臣庋辏回收,這部分內(nèi)存泄露還是比較厲害的

靜態(tài)變量導(dǎo)致的泄露

這個(gè)很簡(jiǎn)單划煮,都知道static修飾的變量會(huì)存于方法區(qū)中送丰,并且在整個(gè)進(jìn)程運(yùn)行中不會(huì)被GC,所以說一旦static持有了context弛秋,那么必須注意泄露的問題
使用static的主要場(chǎng)景就是單例和復(fù)用器躏,看一個(gè)例子

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
    }

}

其實(shí)如果必須要使用單例的話,有一個(gè)非常簡(jiǎn)單的解決方法蟹略,那就是使用ApplicationContext登失,不過此時(shí)啟動(dòng)Activity等功能可能會(huì)受限,所以說要考慮清楚這個(gè)單例是否必要再具體使用挖炬,不能只圖功能實(shí)現(xiàn)

    private constructor(context:Context){
        mContext = context.applicationContext
    }
異步任務(wù)導(dǎo)致的泄露等問題

比方說網(wǎng)絡(luò)請(qǐng)求和一些耗時(shí)的操作揽浙,常規(guī)的處理都是在一個(gè)工作線程中處理,然后等待處理完畢再投遞回主線程中處理
這會(huì)導(dǎo)致兩個(gè)問題:
1.異步回調(diào)的空指針問題意敛,如果你在Activity的onDestroy中手動(dòng)釋放了資源馅巷,那么要求你在回調(diào)中必須處理
2.內(nèi)存泄露,當(dāng)前Activity在執(zhí)行中已經(jīng)退出草姻,但是也只能等待耗時(shí)任務(wù)完成之后令杈,后續(xù)的GC才有可能回收該Activity的資源
這類問題的常見解決方案:
1.Handler相關(guān),需要考慮的就是大部分異步任務(wù)是通過Handler最后回調(diào)會(huì)主線程碴倾,并且Handler本身也可能執(zhí)行延時(shí)任務(wù)

    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){
                //說明當(dāng)前Activity還不應(yīng)該被回收,可以繼續(xù)操作,do something
            }
        }
    }

回調(diào)方面的處理就是通過弱引用來持有Activity,因?yàn)槿跻贸钟械膶?duì)象在只有弱引用持有的情況下跌榔,GC也是會(huì)進(jìn)行回收的异雁,此時(shí)get()獲得的就是null對(duì)象

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

在對(duì)應(yīng)的Activity的onDestroy中移除handler中所有未執(zhí)行的回調(diào)和消息,這樣也可以有效避免延時(shí)任務(wù)的再執(zhí)行
2.Thread類型僧须,這個(gè)和Handler類似纲刀,需要注意的就是后臺(tái)任務(wù)需要在合適的時(shí)機(jī)進(jìn)行終止,避免其肆意執(zhí)行
比方說AsyncTask

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

實(shí)際上就是在onDestroy中終止担平,并且嘗試中斷當(dāng)前線程
對(duì)于普通的線程來說示绊,也是一樣的處理,比方說

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
            //如果你認(rèn)為應(yīng)該強(qiáng)勢(shì)中斷線程暂论,那么你可以
            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()
            //耗時(shí)操作一
            if(isCancelled){
                return
            }
            //耗時(shí)操作二
            if(isCancelled){
                return
            }
            //后續(xù)普通操作
        }

    }

}

換言之面褐,線程有自然終止和強(qiáng)行中斷兩種操作,這里要根據(jù)具體的場(chǎng)景自行選擇使用

注冊(cè)和反注冊(cè)問題

這個(gè)實(shí)際上也是長久持有的問題取胎,比方說EventBus就是static持有展哭,常見于觀察者模式下的單例,廣播是因?yàn)橄到y(tǒng)服務(wù)長期存在的原因闻蛀,總之就是注意注冊(cè)了要反注冊(cè)匪傍,要成對(duì)存在

資源釋放

這個(gè)指的就是一些持有大量資源的類的釋放,比方說IO流和Cursor觉痛,這兩個(gè)最常見

圖片處理

這里實(shí)際上就是說的Bitmap的處理役衡,從上一節(jié)當(dāng)中也可以看到,實(shí)際上圖片內(nèi)存占用是非常大薪棒,所以說合適的圖片處理也是很重要的

圖片選擇問題

考慮圖片加載的使用場(chǎng)景手蝎,比方說同樣是華為手機(jī),但是一個(gè)屏幕密度高盗尸,一個(gè)屏幕密度低柑船,此時(shí)實(shí)際上展示在手機(jī)上面的同一幅圖像素實(shí)際上是不一樣的,此時(shí)簡(jiǎn)單的方案是直接使用一張大圖泼各,然后通過圖片加載庫進(jìn)行壓縮等處理展示
比方說480x480鞍时,處理為160x160和240x240兩種,但是很明顯的扣蜻,最合適的做法應(yīng)該是根據(jù)屏幕密度來下發(fā)具體的圖片逆巍,我們知道網(wǎng)絡(luò)圖片的加載地址來源于服務(wù)端,那么如果我們?cè)谡?qǐng)求接口的時(shí)候帶上當(dāng)前要請(qǐng)求的圖片密度莽使,然后區(qū)別返回xxx/v1/demo.jpg或者xxx/v2/demo.jpg锐极,并且圖片的大小和實(shí)際展示的像素大小一致,這樣是最好的選擇
上述相對(duì)于一個(gè)源圖片來說芳肌,實(shí)際上就是解決了兩個(gè)問題
1.不同密度的圖片本身大小就不同灵再,那么低密度的手機(jī)在進(jìn)行網(wǎng)絡(luò)請(qǐng)求請(qǐng)求圖片的時(shí)候肋层,這個(gè)過程消耗的流量會(huì)少一些,并且加載的速度也會(huì)快一些
2.圖片的尺寸完全匹配翎迁,那么將會(huì)減少多余的壓縮等處理栋猖,也會(huì)加快圖片的展示速度

圖片質(zhì)量問題

先看圖片格式選擇
現(xiàn)在市場(chǎng)上常用的圖片有jpg、png以及webp汪榔,webp的介紹可以看下面這個(gè)鏈接
https://zhuanlan.zhihu.com/p/23648251
下面稍微介紹一下三者的區(qū)別:
jpg:有損壓縮蒲拉,并且沒有Alpha通道,相對(duì)于png來說會(huì)小一點(diǎn)痴腌,非常適合一些沒有Alpha通道的大圖
png:無損壓縮雌团,支持Alpha通道,一般在追求圖片質(zhì)量的時(shí)候可以考慮士聪,適用于一些小圖
webp:Google出品锦援,支持有損和無損壓縮兩種模式,并且圖片大小會(huì)小于png和jpg戚嗅,缺點(diǎn)就是Android原生在4.0之后才支持雨涛,并且透明的在4.2.1之后才會(huì)完美支持,并且webp動(dòng)圖目前只有Fresco支持

圖片像素質(zhì)量
每一個(gè)像素點(diǎn)需要存儲(chǔ)在byte[]中懦胞,那么每一個(gè)像素點(diǎn)的存儲(chǔ)方式也會(huì)影響到圖片的大小

    /**
     * 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];
        }
    }

實(shí)際上關(guān)注RGB_565和ARGB_8888即可替久,在沒有Alpha通道的時(shí)候,使用RGB_565對(duì)于內(nèi)存來說是最好的選擇躏尉,對(duì)比于ARGB_8888來說蚯根,每一個(gè)像素點(diǎn)可以接受2個(gè)字節(jié)的大小,對(duì)于一張圖片來說60 * 60 * 2都可以節(jié)省下7KB左右的大小胀糜,在網(wǎng)絡(luò)圖片為jpg的時(shí)候這個(gè)為最佳選擇颅拦。

對(duì)象分配

個(gè)人總結(jié)其實(shí)就幾個(gè)方面:
1.盡量懶加載,特別是static變量教藻,使用的地方再初始化
2.POJO中的屬性距帅,boolean或者int類型盡量不要用String修飾,這樣會(huì)導(dǎo)致在堆中分配很多多余對(duì)象括堤,int則可以做到盡可能的復(fù)用棧中數(shù)據(jù)
3.String類型操作碌秸,如果有大量拼接操作請(qǐng)使用StringBuilder,實(shí)際上+號(hào)拼接多個(gè)對(duì)象的時(shí)候也是new StringBuilder實(shí)現(xiàn)悄窃,所以說特別在for循環(huán)當(dāng)中讥电,能夠在外部定義一個(gè)StringBuilder對(duì)象處理最優(yōu)

StrictMode

Android提供了工具進(jìn)行上述的一些方面的自動(dòng)檢查,使用也很簡(jiǎn)單轧抗,比方說在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讀寫操作是否在工作線程中進(jìn)行
2.網(wǎng)絡(luò)請(qǐng)求是否在工作線程中進(jìn)行
3.自定義標(biāo)記線程恩敌,可以進(jìn)行慢線程操作的檢查
4.Closeable對(duì)象是否close
5.SQLiteDatabase及Cursor是否close
6.Activity泄露
7.BroadcastReceiver及ServiceConnection導(dǎo)致的Context泄露,主要就是注冊(cè)后是否反注冊(cè)横媚,ServiceConnection因?yàn)閎indService是通過Context作為key緩存的纠炮,所以說也要注意unBindService
月趟。。抗碰。
上述的這些檢查項(xiàng)目是允許開發(fā)者自己選擇的狮斗,從我個(gè)人的角度來說,建議全部開啟弧蝇,畢竟養(yǎng)成良好的編程習(xí)慣要從平時(shí)做起
檢查到問題之后,StrictMode懲罰模式也有打印日志和直接奔潰等幾種行為折砸,推薦通過打印日志的方式來具體定位問題并且進(jìn)行修改

結(jié)論

這一節(jié)主要是一些經(jīng)驗(yàn)之談看疗,關(guān)于內(nèi)存方面的,這一塊需要通過大量的項(xiàng)目實(shí)踐來慢慢感受睦授,但是有一點(diǎn)是確認(rèn)的两芳,良好的編程習(xí)慣應(yīng)該從平時(shí)養(yǎng)成,開發(fā)者應(yīng)該對(duì)自己的代碼有這比較高的要求才行

文章系列:
基本的優(yōu)化總結(jié)(一)
基本的優(yōu)化總結(jié)(二)
基本的優(yōu)化總結(jié)(三)
基本的優(yōu)化總結(jié)(四)
基本的優(yōu)化總結(jié)(五)
基本的優(yōu)化總結(jié)(六)
基本的優(yōu)化總結(jié)(七)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末去枷,一起剝皮案震驚了整個(gè)濱河市怖辆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌删顶,老刑警劉巖竖螃,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逗余,居然都是意外死亡特咆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門录粱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻格,“玉大人,你說我怎么就攤上這事啥繁〔酥埃” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵旗闽,是天一觀的道長酬核。 經(jīng)常有香客問我,道長宪睹,這世上最難降的妖魔是什么愁茁? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮亭病,結(jié)果婚禮上鹅很,老公的妹妹穿的比我還像新娘。我一直安慰自己罪帖,他們只是感情好促煮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布邮屁。 她就那樣靜靜地躺著,像睡著了一般菠齿。 火紅的嫁衣襯著肌膚如雪佑吝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天绳匀,我揣著相機(jī)與錄音芋忿,去河邊找鬼。 笑死疾棵,一個(gè)胖子當(dāng)著我的面吹牛戈钢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播是尔,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼殉了,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了拟枚?” 一聲冷哼從身側(cè)響起薪铜,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恩溅,沒想到半個(gè)月后隔箍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暴匠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鞍恢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片每窖。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帮掉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窒典,到底是詐尸還是另有隱情蟆炊,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布瀑志,位于F島的核電站涩搓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏劈猪。R本人自食惡果不足惜昧甘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望战得。 院中可真熱鬧充边,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肘习,卻和暖如春际乘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漂佩。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工脖含, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人投蝉。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓器赞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親墓拜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344