onPause耗時導致Activity pause timeout for ActivityRecord的ANR分析

activity的幾個生命週期都是在主線程中執行的,因此在主線程中執行耗時操作後再次點擊屏幕按鍵會產生ANR。還有特別需要注意在onPause中儘量不要做過多的耗時操作,可以將耗時操作移動到onStop中,在ActivityStack.java類中,我們可以看到生命週期的超時時間:

  • onPause是500毫秒
  • onStop和onDestory是10秒

 onPause只有500毫秒,根本無法執行較多的釋放清理任務,因此,我們可以將耗時較多的操作放到onStop中,起碼這樣不會在onPause中產生ANR異常

// How long we wait until giving up on the last activity to pause.  This
    // is short because it directly impacts the responsiveness of starting the
    // next activity.
    private static final int PAUSE_TIMEOUT = 500;

    // How long we wait for the activity to tell us it has stopped before
    // giving up.  This is a good amount of time because we really need this
    // from the application in order to get its saved state.
    private static final int STOP_TIMEOUT = 10 * 1000;

    // How long we wait until giving up on an activity telling us it has
    // finished destroying itself.
    private static final int DESTROY_TIMEOUT = 10 * 1000;

 例如下面這兩處,都是在生命週期裏sleep了9秒,onPause就會觸發ANR,而在onStop裏則正常響應:

    @Override
    protected void onPause() {
        super.onPause();
        try {
            Log.d(TAG, "onPause 準備sleep9秒");
            Thread.sleep(9000);
            Log.d(TAG, "onPause sleep9秒完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop start");
        try {
            Log.d(TAG, "onStop 準備sleep9秒");
            Thread.sleep(9000);
            Log.d(TAG, "onStop sleep9秒完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

當出現ANR時,我們可以搜索關鍵字:

WindowManager|ActivityManager|dispatching timed out|NOT RESPONDING|dispatching timed out|pause timeout for ActivityRecord

如果有trace日誌的話可以搜索:
Cmd line|main|crashType|ANR

下面通過如下的例子,驗證下ANR的產生過程:

“在主線程做了耗時操作”並不一定會引起ANR的發生,除非在耗時操作過程中有用戶交互,用戶點擊或者觸摸界面後沒有及時響應纔會造成ANR的發生因爲ANR的意思是應用沒有響應,但是耗時操作實際上並不一定會導致沒有響應。

完整項目可見:https://github.com/buder-cp/CustomView/blob/master/buder_DN_view/buderdn03/src/main/java/com/test/buderdn03/Main2Activity.java
看下面的程序:

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class Main2Activity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private TextView testText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        initView();
        Log.d(TAG, "onCreate start");
    }

    @Override
    protected void onPause() {
        super.onPause();
        try {
            Log.d(TAG, "onPause 準備sleep9秒");
            Thread.sleep(9000);
            Log.d(TAG, "onPause sleep9秒完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop start");
        try {
            Log.d(TAG, "onStop 準備sleep9秒");
            Thread.sleep(9000);
            Log.d(TAG, "onStop sleep9秒完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private void initView() {
        Button btnTest = findViewById(R.id.btn_test);
        testText = findViewById(R.id.tv_test);
        btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                testSleep();
            }
        });

    }

    public void testSleep() {
        //10s之後本應該進行更新ui操作,但是由於此時主線程處於休眠狀態,因此待主線程結束休眠之後纔會進行更新ui操作

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "準備更新text");
                testText.setText("update btn text");
                Log.d(TAG, "更新text完成");
            }
        }, 10000);


        try {
            Log.d(TAG, "準備sleep30秒");
            Thread.sleep(30000);
            Log.d(TAG, "sleep30秒完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d(TAG, "first update");
        testText.setText("This is the first update");

    }
}

運行日誌:

點擊按鈕後,主線程sleep了30秒,中間我們沒有任何手指觸摸操作,30秒後,程序順利完成了textView的文字更新任務,期間並沒有發生ANR。但是如果在等待的30秒期間我們按了返回或者再次點擊按鈕,過5秒就會出現ANR了

出現ANR如圖所示,當然,將點擊中的sleep代碼移動到onPause聲明週期中,同樣會出現ANR。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章