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。