導(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é)(七)