android從放棄到堅持放棄第二課(下)

續第二課( 下)

寫app必須掌握活動的生命週期。

[活動的生命週期]

[返回棧]

android每次啓動的活動會覆蓋在原活動之上,然後點擊Back鍵會銷燬最上層的活動。是使用Task來管理活動,一個任務就是一組存放在棧裏面的活動的集合,這個棧被稱爲返回棧。每當我們按下這個Back或者調用finish()方法去銷燬一個活動,處於棧頂的活動就會出棧。


[活動狀態]

  1. 運行狀態

一個活動位於返回棧棧頂的時候,就是運行狀態,也是系統最不願意回收的狀態的活動。

  1. 暫停狀態

當一個活動不再處於棧頂,但還是可見時,就進入了暫停狀態。如對話框。只有在內存極低的情況下,系統纔會回收這個活動

  1. 停止狀態

當一個活動不處於棧頂且不可見時就進入了停止狀態。系統有可能會回收。

  1. 銷燬狀態

當一個活動從返回棧中移除後就變成了銷燬狀態。系統回收這種狀態的活動,保證內存充足。


[活動的生存期]

Activity類中定義了7個回調方法,覆蓋了活動生命週期的每一個環節。

方法 簡介
onCreate() 每個活動,我們都重寫了這個方法,他會在活動第一次被創建的時候調用。可以用來完成活動的初始化操作。如加載佈局,綁定事件
onStart() 在活動由不可見變爲可見時調用
onResume() 在活動準備好與用戶進行交互的時候調用。此時的活動一定位於返回棧的棧頂,處於運行狀態。
onPause() 這個方法在活動準備去啓動或者恢復另一個活動的時候調用。通常在這個方法中釋放一些消耗CPU的資源,以及保存一些關鍵數據,但這個方法的執行速度一定要快,否則會影響新棧頂活動的使用
onStop() 這個方法在活動不可見的時候調用。它和onPause()區別:啓動的活動是一個對話框式的活動,onStop()不會執行。
onDestory() 這個方法在活動被銷燬之前調用
onRestart() 這個方法在活動由停止狀態變爲運行狀態之前調用

除了onRestart()方法外,都爲兩量對應關係

三種生存期:

生存期 簡介
完整生存期 在onCreate()方法和onDestory()之間所經歷的就是完整生存期
可見生存期 onStart()和onStop()方法之間所經歷的。
前臺生存期 在onResume()和onPause()之間經歷的

都是從加載資源到釋放資源,從而合理的管理資源。

示意圖


[體驗活動的生命週期]

既然是體驗,我們還是重新new project吧,這次創建子活動勾選Launcher Activity,創建NormalActivity,DialogActivity.

編輯activity_normal.xml:

<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is normal activity"
        />

</LinearLayout>

編輯activity_dialog.xml:

<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text = "This is a dialog activity"/>

</LinearLayout>

從名字上可以看出,一個是normal活動,一個是dialog活動(對話框式),但是上面的代碼基本一樣啊。。。。我們需要去AndroidManifest.xml中修改:

        <activity android:name=".DialogActivity"
            android:theme="@android:style/Theme.Dialog">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

我們給它增加了android:theme的屬性。

現在去activity_main.xml中去增加button:

    <Button
        android:id="@+id/start_normal_activity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start normalActivity"
        />

    <Button
        android:id="@+id/start_dialog_activity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start dialogActivity"
        />

最後修改MainActivity中的代碼:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
        Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);

        startNormalActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, NormalActivity.class);
                startActivity(intent);
            }
        });

        startDialogActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, DialogActivity.class);
                startActivity(intent);
            }
        });


    }

    @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, "onDestory");
    }

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

跑路。順帶觀察一下logcat:

好了,你現在已經體驗了一遍完整的生命週期。

我碰到了一個問題,start_dialog_activity這個button運行時除了錯,原因是給dialog這個activity的theme裏面的屬性和DialogActivity繼承的一個AppCompatActivity類不兼容,所以只要把繼承的AppCompatActivity改爲Activity就可以了。


[活動被回收怎麼辦?]

當一個活動進入到了停止狀態,有可能被系統回收。這樣的話,返回之前的處於停止狀態的活動是可以的,只不過不會執行onRestart() 方法而是執行的onCreate()方法,也就是說這種情況下,返回之前 被回收的活動是會被重新創建的。

不過問題來了,這樣臨時的數據會丟失,不過問題不大。Activity中提供了onSaveInstanceState()回調方法,這個方法可以保證在活動被回收之前一定被調用,因此我們可以通過這個方法來保存臨時數據。

onSaveInstanceState()方法攜帶一個Bundle類型的參數,Bundle類提供了一些列方法保存數據,putString)(),putInt()等等,每個保存方法需要傳入兩個參數,第一個參數是鍵,用於取值,第二個是真正要保存的數據。和Intent的差不多。

MainActivity:

    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        String tempData = "Something i just typed";
        outState.putString("data_key",tempData);
    }

保存完數據,但是該如何取出數據呢?

onCreate()方法中有一個Bundle類型的參數,就靠它

MainActivity:

Log.d(TAG, "onCreate");
if(savedInstanceState != null){
  String tempData = savedInstanceState.getString("data_key");
  Log.d(TAG,tempData);
}

而且我們還可以將Bundle對象存放在Intent中,到了目標活動中再取出Bundle,再從Bundle中一一取出數據。


[活動的啓動模式]

啓動模式分爲四種:

  1. standard
  2. singleTop
  3. singleTask
  4. singleInstance

可以在androidManifest.xml中通過給標籤中指定android:launchMode屬性來選擇。


1.standard

活動默認的啓動模式。因此,我們之前所使用過的都是standard模式。每當啓動一個新的活動,它就會在返回棧中入棧,並處於棧頂的位置。對於standard模式下的活動,系統不會在乎這個活動是否已在返回棧中存在,每次啓動都會創建該活動的一個新的實例。

打開之前的ActivityTest項目。

修改FirstActivity:

public class FirstActivity extends AppCompatActivity {

    private static final String Tag = "FirstActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.first_layout);

        Log.d(Tag, this.toString());
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Toast.makeText(FirstActivity.this, "You click me", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(FirstActivity.this , FirstActivity.class);
                startActivity(intent);
            }
        });

        Button button2 = (Button) findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                String data = "Hello huchi";
                Intent intentString = new Intent(FirstActivity.this, SecondActivity.class);
                intentString.putExtra("extra_data",data);
                //startActivity(intentString);
                startActivityForResult(intentString, 1);


            }
        });

        Button button4 = (Button) findViewById(R.id.button_4);
        button4.setOnClickListener(new View.OnClickListener(){
            @Override
            public  void onClick(View v){
                Intent intentSecond = new Intent("com.example.wrjjrw.activitytest.ACTION_START");
                intentSecond.addCategory("com.example.wrjjrw.activitytest.MY_CATEGORY");
                intentSecond.putExtra("extra_data","error");
                startActivity(intentSecond);
            }
        });


        Button button5 = (Button) findViewById(R.id.button_5);
        button5.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Intent intentToBaidu = new Intent(Intent.ACTION_VIEW);
                intentToBaidu.setData(Uri.parse("http://blog.csdn.net/jaywrzz/article/details/65937639"));
                startActivity(intentToBaidu);
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case R.id.add_item:
                Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this, "You clicked Remove" ,Toast.LENGTH_SHORT).show();
                break;
            case R.id.huchi_item:
                Toast.makeText(this, "I love huchi,too", Toast.LENGTH_LONG).show();
                break;
            default:
        }
        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode){
            case 1:
                if(resultCode == RESULT_OK){
                    String returnedData = data.getStringExtra("data_return");
                    Log.d("FirstActivity",returnedData);
                }
                break;
            default:
        }
    }
}

我們看到他可以啓動他自己,跑路。

03-30 12:38:54.424 7273-7273/com.example.wrjjrw.activitytest D/FirstActivity: com.example.wrjjrw.activitytest.FirstActivity@42900c50
03-30 12:38:59.485 7273-7273/com.example.wrjjrw.activitytest D/FirstActivity: com.example.wrjjrw.activitytest.FirstActivity@4293a0c8

發現如果打開它自己三下,也就需要

2.singleTop

似乎standard不是很合理。singleTop在啓動活動時如果發現返回棧的棧頂已是該活動,則認爲可以直接使用它,不會再創建新的活動實例。

修改AndroidManifest.xml

        <activity
            android:name=".FirstActivity"
            android:launchMode="singleTop"
            android:label="This is huchi&apos;s FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

發現無論點多少次logcat都沒有打印信息了。

不過在FirstActivity並未處於棧頂時,這時再啓動FirstActivity,還是會創建新的實例的。

我們發現在FirstActivity和SecondActivity兩個活動中跳來跳去可以在logcat看到打印的消息。


3.singleTask

singleTask模式可以解決上述問題。每次啓動活動時,系統會在返回棧中檢查是否存在該活動的實例,如果發現已經存在則直接使用該實例,並把在這個活動上面的所有活動統統出棧,如果沒有發現就會創建一個新的活動實例。

修改FirstActivity:

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

修改SecondActivity,添加onDestroy():

public class SecondActivity extends AppCompatActivity {

    private static  final  String Tag = "SecondActivity";
//    @Override
//    public void onBackPressed() {
//        Intent intent = new Intent();
//        intent.putExtra("data_return", "back_to_firstActivity");
//        setResult(RESULT_OK, intent);
//        finish();
//    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        Button button3 = (Button) findViewById(R.id.button_3);
        button3.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Intent intentFirst = new Intent(SecondActivity.this, FirstActivity.class);
                startActivity(intentFirst);
            }
        });

        Button buttonFinishSecond = (Button) findViewById(R.id.button_finish);
        buttonFinishSecond.setOnClickListener(new View.OnClickListener(){
            @Override
            public  void onClick(View v){
                Intent intent = new Intent();
                intent.putExtra("data_return","byebye huchi");
                setResult(RESULT_OK, intent);
                finish();
            }
        });

        Intent intent = getIntent();
        String data = intent.getStringExtra("extra_data");
        Log.d("SecondActivity",data);

    }

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

跑路。觀察logcat


4.singleInstance

最特殊最複雜的模式。

會啓動一個新的返回棧來管理這個活動(如果singleTask模式下指定了不同的taskAffinity).singleInstance 可以實現其他程序和我們的程序共享一個活動的實例。在這種模式下會有一個單獨的返回棧來管理這個活動,不管是哪個應用程序來訪這個活動,都公用同一個返回棧。

實踐出真知:

修改AndroidManifest.xml:

        <activity android:name=".SecondActivity"
            android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="com.example.wrjjrw.activitytest.ACTION_START" />
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="com.example.wrjjrw.activitytest.MY_CATEGORY" />
            </intent-filter>
        </activity>

FirstActivity:

        Log.d(Tag, "Task id is " + getTaskId());

        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Toast.makeText(FirstActivity.this, "You click me", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(FirstActivity.this , SecondActivity.class);
                startActivity(intent);
            }
        });

修改SecondActivity:

        Log.d(Tag, "Task id is "+ getTaskId());

        Button button3 = (Button) findViewById(R.id.button_3);
        button3.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Intent intentFirst = new Intent(SecondActivity.this, ThirdActivity.class);
                startActivity(intentFirst);
            }
        });

activity_third:

    <Button
        android:id="@+id/button_to_first"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="return first"
        ></Button>

修改ThirdActivity

        Log.d(Tag, "Task id is "+ getTaskId());

        Button button3 = (Button) findViewById(R.id.button_3);
        button3.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Intent intentFirst = new Intent(SecondActivity.this, ThirdActivity.class);
                startActivity(intentFirst);
            }
        });

我們發現從FirstActivity跳轉到SecondActivity,再跳轉到ThirdActivity後,此時按back鍵返回回到的就是FirstActivity。


[實踐出真知]

[知曉當前是哪一個活動?]

在集體編程時,可能會找不到界面對應的活動。

新建一個BaseActivity:

public class BaseActivity extends AppCompatActivity {

    private static final String Tag = "BaseActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(Tag, getClass().getSimpleName());
    }
}

因爲BaseActivity繼承的是AppCompatActivity,我們可以讓其他三個活動繼承BaseActivity。也就是說其他活動onCreate的時候,就會執行父類的onCreate,也就都會執行Log.d(Tag, getClass().getSimpleName());這句話,然後我們就知道哪個界面對應的是哪個活動了。

03-30 14:09:16.085 9459-9459/com.example.wrjjrw.activitytest D/BaseActivity: FirstActivity
03-30 14:09:19.440 9459-9459/com.example.wrjjrw.activitytest D/BaseActivity: SecondActivity
03-30 14:09:21.617 9459-9459/com.example.wrjjrw.activitytest D/BaseActivity: ThirdActivity

[隨時隨地退出程序]

只需要一個專門的集合類對所有的活動進行管理。

BaseActivity:


public class ActivityCollector {

    public static List<Activity> activities = new ArrayList<>();

    public static void addActivity(Activity activity){
        activities.add(activity);
    }

    public static void removeActivity(Activity activity){
        activities.remove(activity);
    }

    public static void finishAll(){
        for(Activity activity: activities){
            if(!activity.isFinishing())
                activity.finish();
        }
    }

}

BaseActivity:

public class BaseActivity extends AppCompatActivity {

    private static final String Tag = "BaseActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(Tag, getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

}

這樣就可以把存在的活動放在一個數組裏。

以後不管想在什麼時候地方退出程序,只要調用ActivityCollector.finishAll().

    public static void finishAll(){
        for(Activity activity: activities){
            if(!activity.isFinishing())
                activity.finish();
        }
        android.os.Process.killProcess(android.os.Process.myPid());
    }

我進行了一番嘗試:

        Button button10 = (Button) findViewById(R.id.button_to_finish);
        button10.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                ActivityCollector.finishAll();
            }
        });

美好的。

[啓動活動的最佳寫法]

雖然我們寫的intent不管是語法上還是規範上都符合。但在對接的時候總是會有疑問???不清楚這個活動需要哪些數據。

修改SecondActivity:

    public static void actionStart(Context context, String data1, String data2){
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1",data1);
        intent.putExtra("param2",data2);
        context.startActivity(intent);
    }

修改FirstActivity:

        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Toast.makeText(FirstActivity.this, "You click me", Toast.LENGTH_SHORT).show();
                SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
            }
        });

[問題]

問:爲啥我自動生成的都不是Linearlayout?

答:Click me,下一次學習筆記中將具體學習。

問:啥是taskAffinity?

答:Click me.

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