安卓-Activity生命週期

一、簡述

android是使用任務來管理活動的,一個任務就是一組存放在棧中的活動的集合。

1.活動有四個狀態:
1)運行狀態:活動位於返回棧的棧頂
2)暫停狀態:活動不處於棧頂位置,但是可見
3)停止狀態:活動不處於棧頂位置,且完全不可見
4)銷燬狀態:當一個活動從返回棧中移除

2.活動生存週期
Activity中定義了7各回調方法
onCreate():活動第一次創建時調用,完成初始化操作:加載佈局、綁定事件
onStart():活動由不可見變爲可見時調用
onResume():活動準備好和用戶交互時調用。處於返回棧的棧頂,並且處於運行狀態
onPause():系統準備去啓動或者恢復另一個活動時調用。比如:上面彈出各對話框
onStop():活動在完全不可見的時候調用
onDestroy():活動被銷燬之前調用
onRestart():活動由停止狀態變爲運行狀態

生命週期又可分爲3種生存期
完整生存期:onCreate()到onDestroy()之間
可見生存期:onStart()到onStop()之間
前臺生存期:onResume()和onPause()之間

總結下狀態的變化:

一個Activity從創建到顯示經歷:onCreate()  onStart() onResume()
一個Activity上彈出一個全屏的Activity,經歷:onPause  onStop,上面的銷燬,onRestart onStart  onResume
一個Activity上彈出一個對話框,下面的Activity經歷:onPause  ,此時點擊返回鍵,下面的Activity執行onResume,再點擊返回鍵,Activity執行onPause  onStop  onDestroy

下面也引用網上的一張圖:


上面這張圖是各個文章中引用最多的,詳細說明了各個狀態。

二、測試各種情況

1)測試一個Activity正常啓動時

測試結果:

02-23 18:55:55.524 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onCreate: 
02-23 18:55:55.744 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onStart: 
02-23 18:55:55.744 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onResume:

可以看到Activity在啓動的過程中經歷了:onCreate->onStart->onResume

2)從後臺切換到前臺

測試結果:

02-23 19:21:51.954 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onRestart: 
02-23 19:21:51.954 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onStart:

02-23 19:21:51.964 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onResume:

02-23 19:21:52.004 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged:

可以看到Activity經歷了:onRestart->onStart->onResume->onWindowFocusChanged

3)全屏的Activity跳轉且不透明

測試結果:

02-23 19:38:31.854 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onPause: 
02-23 19:38:31.874 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged: 
02-23 19:38:31.904 20215-20215/com.mobile.cdtx.blog D/SecondDemoActivity: onCreate: 
02-23 19:38:31.994 20215-20215/com.mobile.cdtx.blog D/SecondDemoActivity: onStart: 
02-23 19:38:31.994 20215-20215/com.mobile.cdtx.blog D/SecondDemoActivity: onResume: 
02-23 19:38:32.084 20215-20215/com.mobile.cdtx.blog D/SecondDemoActivity: onWindowFocusChanged: 
02-23 19:38:32.474 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onStop:

其中ActivityLifeActivity是下面的界面,SecondDemoActivity是跳轉後的界面

ActivityLifeActivity經歷了:onPause->onWindowFocusChanged->onStop

SecondDemoActivity經歷了:onCreate->onStart->onResume->onWindowFocusChanged

4)全屏的Activity返回到下面的Activity

測試結果:

02-23 19:31:59.854 8605-8605/com.mobile.cdtx.blog D/SecondDemoActivity: onPause:
02-23 19:31:59.864 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onRestart: 
02-23 19:31:59.864 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onStart: 
02-23 19:31:59.864 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onResume: 
02-23 19:31:59.894 8605-8605/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged: 
02-23 19:31:59.964 8605-8605/com.mobile.cdtx.blog D/SecondDemoActivity: onWindowFocusChanged: 
02-23 19:32:00.284 8605-8605/com.mobile.cdtx.blog D/SecondDemoActivity: onStop: 
02-23 19:32:00.284 8605-8605/com.mobile.cdtx.blog D/SecondDemoActivity: onDestroy:

其中,SecondDemoActivity是上面的界面,ActivityLifeActivity是下面的界面

SecondDemoActivity經歷了:onPause->onWindowFocusChanged->onStop->onDestroy

ActivityLifeActivity經歷了:onRestart->onStart->onResume->onWindowFocusChanged

5)從前臺切換到後臺

測試結果:

02-23 19:43:37.854 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onPause: 
02-23 19:43:37.874 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged: 
02-23 19:43:38.354 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onStop:

ActivityLifeActivity從前臺切換到後臺經歷了:onPause->onWindowFocusChanged->onStop

6)Activity上彈出Toast

測試結果:

未打印任何日誌,這說明窗口焦點和Activity未受任何影響

7)Activity上彈出對話框

測試結果:

02-23 19:49:10.394 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged:

可以看到Activity只經歷了:onWindowFocusChanged

8)Activity上彈出的對話框消失

測試結果:

02-23 19:50:34.684 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged:

可以看到Activity只經歷了:onWindowFocusChanged

9)彈出半透明對話框

測試結果:

02-23 19:52:23.124 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onPause: 
02-23 19:52:23.144 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged: 

可以看到ActivityLifeActivity經歷了:onPause->onWindowFocusChanged

10)彈出的半透明對話框消失

02-23 19:54:18.944 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onResume: 
02-23 19:54:18.964 20215-20215/com.mobile.cdtx.blog D/ActivityLifeActivity: onWindowFocusChanged:

ActivityLifeActivity經歷了:onResume->onWindowFocusChanged

11)改變屏幕方向

然後在“Settings->Display”中,將“Auto-rotate Screen”一項選中,表明可以自動根據方向旋轉屏幕,中文設置中顯示的是:顯示->自動旋轉屏幕

測試結果:

02-23 20:03:12.884 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onPause: 
02-23 20:03:12.884 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onStop: 
02-23 20:03:12.884 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onDestroy: 
02-23 20:03:12.924 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onCreate: 
02-23 20:03:13.044 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onStart: 
02-23 20:03:13.044 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onRestoreInstanceState: 
02-23 20:03:13.044 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onResume: 
02-23 20:03:13.124 9747-9747/com.mobile.cdtx.blog D/SecondDemoActivity: onWindowFocusChanged:

可以看到Activity經歷了:onPause->onStop->onDestroy->onCreate->onStart->onRestoreInstanceState->onResume->onWindowFocusChanged

其中onRestoreInstanceState用於恢復保存的數據

上面的數據說明經歷銷燬到重建的過程,怎麼樣纔可以避免這種情況呢?

1.AndroidManifest.xml中聲明:<uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>

2.在Activity的註冊中設置:android:configChanges="orientation|screenSize“屬性

3.在Activity中重寫下面的方法:

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
        {
            Log.d(TAG, "onConfigurationChanged:橫屏");// 獲取橫屏
        } else if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        {// 獲取豎屏
            Log.d(TAG, "onConfigurationChanged:豎屏");// 獲取橫屏
        }
    }
測試結果:

02-23 20:27:29.034 9379-9379/com.mobile.cdtx.blog D/SecondDemoActivity: onConfigurationChanged:橫屏

可以看到Activity並沒有經歷銷燬到重建的過程。

有些文章上說android:configChanges="orientation“這個屬性這樣設置,但是在新高版本的API中不行仍然會經歷從銷燬到重建的過程。自從Android 3.2(API 13),screen size也開始跟着設備的橫豎切換而改變。
所以,在AndroidManifest.xml裏設置的MiniSdkVersion和 TargetSdkVersion屬性大於等於13的情況下,如果你想阻止程序在運行時重新加載Activity,除了設置"orientation",還必須設置"ScreenSize"。

如果不想讓屏幕發生旋轉怎麼辦呢?

指定屬性:

android:screenOrientation="portrait"或android:screenOrientation="landscape"

如果<activity>配置了android:screenOrientation屬性,則會使android:configChanges="orientation"失效

測試代碼中也增加了onSaveInstanceState方法的測試,但是發現Activity即將被銷燬的時候並沒有執行這個方法,這是爲什麼呢?

當Activity只執行onPause方法時(Activity a打開一個透明Activity b)這時候如果App設置的targetVersion大於android3.0則不會執行onSaveInstanceState方法。

以上測試結果已經可以說明常見的調用情況了,大家看着測試數據多體會多測試。

三、測試代碼
AndroidManifest.xml中用到的關鍵代碼

<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.CHANGE_CONFIGURATIONS"/>
....
<activity android:name=".main.activity.ActivityLifeActivity">
</activity>

<!--如果指定了android:screenOrientation="landscape"這個屬性,則屏幕不會發生旋轉-->
<activity android:name=".main.activity.SecondDemoActivity" android:configChanges="orientation|screenSize" >
</activity>
主測試界面佈局文件activity_life.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/activity_life"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/id_btn_jump"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="模擬界面跳轉"/>
    <Button
        android:id="@+id/id_btn_toast"
        android:textAllCaps="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="彈出Toast提示"/>
    <Button
        android:id="@+id/id_btn_dialog"
        android:textAllCaps="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="彈出對話框提示"/>
    <Button
        android:id="@+id/id_btn_home"
        android:textAllCaps="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="模擬跳到主屏幕界面"/>
    <Button
        android:id="@+id/id_btn_call"
        android:textAllCaps="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="模擬打電話"/>

</LinearLayout>

主界面代碼:

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.mobile.cdtx.blog.R;

//測試Activity的生命週期
public class ActivityLifeActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "ActivityLifeActivity";
    private Button btnJump, btnToast, btnDialog, btnHome, btnCall;
    Intent intent;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: ");
        setContentView(R.layout.activity_life);
        initView();
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart: ");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: ");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: ");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop: ");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart: ");
    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        super.onSaveInstanceState(outState, outPersistentState);
        Log.d(TAG, "onSaveInstanceState: ");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState: ");
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        Log.d(TAG, "onWindowFocusChanged: ");
    }

    //當指定了android:configChanges="orientation"後,方向改變時onConfigurationChanged被調用
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d(TAG, "onConfigurationChanged:");
    }

    //控件初始化
    private void initView() {
        btnJump = (Button) findViewById(R.id.id_btn_jump);
        btnJump.setOnClickListener(this);
        btnToast = (Button) findViewById(R.id.id_btn_toast);
        btnToast.setOnClickListener(this);
        btnDialog = (Button) findViewById(R.id.id_btn_dialog);
        btnDialog.setOnClickListener(this);
        btnHome = (Button) findViewById(R.id.id_btn_home);
        btnHome.setOnClickListener(this);
        btnCall = (Button) findViewById(R.id.id_btn_call);
        btnCall.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.id_btn_jump:
                startActivity(new Intent(ActivityLifeActivity.this, SecondDemoActivity.class));
                break;
            case R.id.id_btn_toast:
                Toast.makeText(this, "測試Toast", Toast.LENGTH_SHORT).show();
                break;
            case R.id.id_btn_dialog:
                new AlertDialog.Builder(this)
                        .setTitle("標題")
                        .setMessage("簡單消息框")
                        .setPositiveButton("確定", null)
                        .show();
                break;
            case R.id.id_btn_home:
                Intent MyIntent = new Intent(Intent.ACTION_MAIN);
                MyIntent.addCategory(Intent.CATEGORY_HOME);
                startActivity(MyIntent);
                break;
            case R.id.id_btn_call:
                intent = new Intent(Intent.ACTION_CALL);
                Uri data = Uri.parse("tel:1234567890");
                intent.setData(data);
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(ActivityLifeActivity.this, new String[] {Manifest.permission.CALL_PHONE},1);
                    return;
                }else{
                    startActivity(intent);
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                intent = new Intent(Intent.ACTION_CALL);
                Uri data = Uri.parse("tel:1234567890");
                intent.setData(data);
                startActivity(intent);
            } else {
                Toast.makeText(this, "權限被拒絕", Toast.LENGTH_SHORT).show();
            }
            return;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}
運行效果圖:

模擬界面跳轉的界面的佈局文件:

activity_second_demo.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_second_demo"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</RelativeLayout>

佈局很簡單什麼控件都沒,主要是在代碼中打印關鍵方法。

模擬界面跳轉的界面的代碼:

import android.content.res.Configuration;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.mobile.cdtx.blog.R;

public class SecondDemoActivity extends AppCompatActivity {
    private static final String TAG = "SecondDemoActivity";

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

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart: ");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: ");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: ");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop: ");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart: ");
    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        super.onSaveInstanceState(outState, outPersistentState);
        Log.d(TAG, "onSaveInstanceState: ");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState: ");
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        Log.d(TAG, "onWindowFocusChanged: ");
    }

    //當指定了android:configChanges="orientation"後,方向改變時onConfigurationChanged被調用
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
        {
            Log.d(TAG, "onConfigurationChanged:橫屏");// 獲取橫屏
        } else if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        {// 獲取豎屏
            Log.d(TAG, "onConfigurationChanged:豎屏");// 獲取橫屏
        }
    }
}





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