Android常見導(dǎo)致內(nèi)存問題的案例

常見導(dǎo)致內(nèi)存問題的案例

1. Handler的使用

    private val handler = Handler(Looper.getMainLooper(), object : Handler.Callback {
        override fun handleMessage(msg: Message): Boolean {
            //Looper內(nèi)的message -> handler -> 匿名內(nèi)部類 -> activity
            Log.d(TAG, "handleMessage")
            TAG = "Callback"
            return true;
        }
    })

    private val handler1 = object:Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message) {
            TAG = "handler1"
            super.handleMessage(msg)
        }
    }
    
    inner class InnerHandler(context: Context?) : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            TAG = "inner class"
        }
    }
    
    handler.postDelayed(Runnable {
        TAG = ""
    }, 200000)

    handler.sendEmptyMessageDelayed(0, 200000)

在 Handler 代碼里 android.os.Handler#enqueueMessage 很明顯能看到 message 持有 Handler 的引用。因為一個線程只有一個 Looper , 但是可以有多個 Handler ,所以 message 需要持有 Handler 引用來分發(fā)到指定的 Handler 。

然而上面三種寫法都有導(dǎo)致 Handler 持有外部 Activity 的引用。

第一種是 object 為 實現(xiàn)了 Handler.Callback 接口的匿名內(nèi)部類

第二種是 object 為繼承了 Handler 的 匿名內(nèi)部類

第三種是 InnerHandler 為繼承了 Handler 的內(nèi)部類

所以只要消息一直存在于主線程,就會導(dǎo)致 Activity 不能釋放,導(dǎo)致了內(nèi)存泄漏。

解決方法:

//靜態(tài)內(nèi)部類 kotlin 也為了避免內(nèi)部類持有導(dǎo)致的內(nèi)存泄漏頻繁發(fā)生,所以 默認(rèn)一個 class 代表的是靜態(tài)內(nèi)部類,不持有外部引用
//inner class 才是內(nèi)部類
class Handler2(context: Context?) : Handler(Looper.getMainLooper()) {
  private val weakReference: WeakReference<Context> = WeakReference(context)
    override fun handleMessage(msg: Message) {
    //TAG = "weakReference"
    weakReference.get()?.let {
      Log.e("", "")
    }
  }
}

//如果 Message 在退出界面后可以忽略不處理時,可以選擇移除消息
override fun onDestroy() {
  super.onDestroy()
    handler.removeCallbacksAndMessages(null)
}


2. Static/單例模式

Singleton.getInstance(MainActivity@this)

public class Singleton {
    private static Singleton mInstance;

    private Context context;

    private Singleton(Context context) {
        this.context = context;
    }

    public static Singleton getInstance(Context context){
        if(mInstance == null){
            mInstance = new Singleton(context);
        }
        return mInstance;
    }

}

一般情況下,靜態(tài)變量 和 Class 與 ClassLoader 的生命周期,進(jìn)程的生命周期一致。上訴寫法會導(dǎo)致 第一次調(diào)用 getInstance 傳入的activity 不會釋放,從而導(dǎo)致內(nèi)存泄漏。所以 Static 關(guān)鍵字修飾的成員變量要注意引用關(guān)系。

解決方案:

  1. 盡量不在單例里構(gòu)造函數(shù)使用 context , 如果一定要用的話,可以傳入 ApplicationContext 因為它與進(jìn)程的生命周期一致。大部分情況下不會涉及界面的修改, ApplicationContext 就足夠了。
  2. 如果實在非要使用 Activity 的 context,可以使用軟引用。

3. 非靜態(tài)匿名內(nèi)部類

val thread = Thread(mRunnable)
thread.start()
  
private val mRunnable = object :Runnable {
  override fun run() {
    try {
      TAG = "mRunnable"
        Thread.sleep(200000);
    }catch (e: Exception){
      e.printStackTrace()
    }
  }
}

mRunnable 為非靜態(tài)匿名內(nèi)部類實例,持有了外部引用,與 handler 例子類似導(dǎo)致了泄漏。

解決方案:

JAVA 中 設(shè)置 mRunnable 為靜態(tài) 。

kotlin 編譯階段有優(yōu)化,只要不掉用外部變量,則不會持有引用。

4.屬性動畫

anim = ValueAnimator.ofFloat(0f, 1f)
anim.duration = 30000

animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f)
animator.duration = 500000
animator.start()

主要原因在于 android.animation.AnimationHandler#sAnimatorHandler 是 static 的,導(dǎo)致了內(nèi)存泄漏。

可以按如下順序跟代碼:

android.animation.ValueAnimator#cancel
android.animation.ValueAnimator#endAnimation
android.animation.ValueAnimator#removeAnimationCallback
android.animation.ValueAnimator#getAnimationHandler
android.animation.AnimationHandler#getInstance
android.animation.AnimationHandler#sAnimatorHandler

解決方案:

    override fun onDestroy() {
        super.onDestroy()
        anim.cancel()
        animator.cancel()
    }

5.資源未關(guān)閉

BraodcastReceiver,ContentObserver,F(xiàn)ile,游標(biāo) Cursor,Stream 等資源未及時解注冊和釋放

解決方案:

及時解注冊。資源及時釋放。有些語法糖比如Java 1.7 提供的try-with-resource。以及Kotlin FileReadWrite.kt 大多實現(xiàn)了kotlin.io.CloseableKt#use

6.自定義View onDraw 每次new對象

@Override
protected void onDraw(Canvas canvas) {
        Paint paint = new Paint();
}

onDraw執(zhí)行非常頻繁,如果在里面new 對象會導(dǎo)致頻繁的申請內(nèi)存和釋放。另外一方面,頻繁的 GC 也會導(dǎo)致卡頓。

解決方案:

private Paint mPaint;
 
public TestView(Context context) {
        super(context);
    mPaint = new Paint();
}

7. 所有圖片都預(yù)先加載

 //mJustView 其實只有部分機(jī)型收到廣播后才需要add到window上,然而這里直接在初始化時就已經(jīng)加載到內(nèi)存里了。
private void makeJustView() {
        RelativeLayout root = (RelativeLayout) View.inflate(mContext, R.layout.eye_layout, null);
        mConfrim = (Button) root.findViewById(R.id.confirm);
        mConfrim.setOnClickListener(this);
        mJustView = root;
}
private void init() {
        makeJustView();
}

解決方案:

//使用懶加載,僅有部分機(jī)型收到廣播后,才會加載這張圖,對于其他機(jī)型這部分內(nèi)存就省下了
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context conmTextView, Intent intent) {
                showProtectView(getJustView());
        }
    }
};

private View getJustView(){
        if(mJustView == null){
                makeJustView();
        }
        return mJustView;
}
private void init() {
        
}

8. 圖片放錯目錄

假設(shè)本應(yīng)該放在xhdpi的圖片 放在了mdpi目錄下,在xhdpi的機(jī)器上運行時,長寬會被放大兩倍,相應(yīng)的內(nèi)存會變成兩倍。

直接放錯的概率不到,比較容易發(fā)生在換圖,UI只提供了一張圖什么的。

9. XML布局

有些view可能在布局里只顯示一次的,比如引導(dǎo)圖,其實是可以使用 ViewStub。代替VISIBLE/GONE的,需要時再加載。

imageView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);

10.Bitmap使用

https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=403263974&idx=1&sn=b0315addbc47f3c38e65d9c633a12cd6&scene=21#wechat_redirect

  • inSampleSize 采樣加載bitmap
BitmapFactory.Options options = new Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options);
  • Matrix 矩陣 小圖放大,內(nèi)存只有采樣出來的大小,但是顯示效果是放大的。

    Matrix matrix = new Matrix();
    matrix.postScale(2, 2, 0, 0);
    imageView.setImageMatrix(matrix);
    imageView.setScaleType(ScaleType.MATRIX);
    imageView.setImageBitmap(bitmap);
    
  • Bitmap的像素格式

    格式 描述
    ALPHA_8 只有一個alpha通道
    ARGB_4444 這個從API 13開始不建議使用,因為質(zhì)量太差
    ARGB_8888 ARGB四個通道,每個通道8bit,默認(rèn)。
    RGB_565 每個像素占2Byte,其中紅色占5bit,綠色占6bit,藍(lán)色占5bit。不需要 alpha 時資源為jpg時用這個比較理想
  • 幀動畫 比如 loading動畫 //用自定義view 代替

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容