Android中launcherMode="singleTask"詳解【android源碼解析六】

            android中launcherMode有4中屬性:standard(默認), singleTop,singleTask和 singleInstance;網上有好多例子講解這四種關係的:下面我列舉幾個鏈接:

       大明原創---->轉載請標明出處:http://blog.csdn.net/wdaming1986/article/details/7304191

        http://www.cnblogs.com/xiaoQLu/archive/2011/09/29/2195742.html

        http://marshal.easymorse.com/archives/2950

        http://blog.csdn.net/infsafe/article/details/5666964

        http://www.j2megame.com/html/xwzx/ty/2027.html

        等等。我不一一列舉;

       下面是我對singleTask的一點拙見,希望分享一下給大家:

        最近研究android瀏覽器browser,這個BrowserActivity的launcherMode="singleTask",因爲browser不斷地啓動自己,所以要求這個棧中保持只能有一個自己的實例,就像別人總結的這樣“注意singleTask模式的Activity不管是位於棧頂還是棧底,再次運行這個Activity時,都會destory掉它上面的Activity來保證整個棧中只有一個自己,切記切記”這點是毋庸置疑的。

       問題:browser的launcherMode=“singleTask”,browser上網的時候,遇到播放視頻的鏈接,就會通過隱式intent方式跳轉找Gallery3D中的MovieView這個類來播放視頻,這時候如果你點擊home鍵,再點擊browser,你會發現MovieView這個類已經銷燬不存在了,而不會像保存這個MovieView的類對象,給客戶帶來的用戶體驗特別的不好。用戶還得從新下載剛纔的視頻。不支持斷點播放。爲了驗證這一問題,我專門寫了個小例子來說明activity之間的生命週期:

       (1)先建一個App1的工程。包名爲:com.cn.daming.app1,包裏面有一個類:App1Activity。裏面的代碼如下:

package com.cn.daming.app1;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class App1Activity extends Activity {

	private Button mButton = null;
	
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.v("daming", "App1Activity--11-->onCreate()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
        mButton = (Button)findViewById(R.id.button1);
        mButton.setOnClickListener(new OnClickListener(){

			public void onClick(View arg0) {
				Intent intent = new Intent();
				intent.setClassName("com.cn.daing.app2", "com.cn.daing.app2.App2Activity");
				startActivity(intent);
			}
        });
    }

	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.v("daming", "App1Activity--11-->onDestroy()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onPause() {
		super.onPause();
		Log.v("daming", "App1Activity--11-->onPause()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		Log.v("daming", "App1Activity--11-->onRestart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onResume() {
		super.onResume();
		Log.v("daming", "App1Activity--11-->onResume()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStart() {
		super.onStart();
		Log.v("daming", "App1Activity--11-->onStart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStop() {
		super.onStop();
		Log.v("daming", "App1Activity--11-->onStop()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		Log.v("daming", "App1Activity--11-->onNewIntent()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		Log.v("daming", "App1Activity--11-->onSaveInstanceState()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}
}

       manifest的屬性爲:android:launchMode="singleTask"     android:alwaysRetainTaskState="true"

 

      (2)在建一個App2的工程,包名:com.cn.daing.app2  ;類名爲:App2Activity;代碼爲:

package com.cn.daing.app2;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class App2Activity extends Activity {
	
	private Button mButton = null;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.v("daming", "App2Activity--22-->onCreate()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
        mButton = (Button)findViewById(R.id.button1);
        mButton.setOnClickListener(new OnClickListener(){

			public void onClick(View arg0) {
				Intent intent = new Intent();
				intent.setClassName("com.cn.daming.app1", "com.cn.daming.app1.App1Activity");
				startActivity(intent);
			}
        });
    }

	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.v("daming", "App2Activity--22-->onDestroy()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		Log.v("daming", "App2Activity--22-->onNewIntent()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onPause() {
		super.onPause();
		Log.v("daming", "App2Activity--22-->onPause()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		Log.v("daming", "App2Activity--22-->onRestart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onResume() {
		super.onResume();
		Log.v("daming", "App2Activity--22-->onResume()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		Log.v("daming", "App2Activity--22-->onSaveInstanceState()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStart() {
		super.onStart();
		Log.v("daming", "App2Activity--22-->onStart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStop() {
		super.onStop();
		Log.v("daming", "App2Activity--22-->onStop()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}
    
}

     manifest的屬性爲默認值;


     (3)然後啓動App1---->點擊調用第二個App2button按鈕的log: 

                       

      從log中可以看出:從App1到App2是在一個task棧中,值都是33,並且App1Activity是位於棧底。根節點的。點擊按鈕的時候,調用App1Activity的onSaveInstanceState()----->onPause()----->App2Activity的onCreate()----->onStart()---->onResume()---->App1Activity的onStop()方法。

 

     (4)點擊調用第一個App1---->button按鈕;

               

   

        看一下log:

 還在一個棧中,數字變了因爲我退出後從新操作的,把以前的log清空了,這時候發現了一個奇怪的現象,啓動第一個App1的時候,App2從onStop()---->onDestroy()了,徹底銷燬了;這個對象就不存在了。這也就驗證了browser點擊視頻播放,點擊home鍵,再點擊browser,視頻播放停止的現象的,android的singleTask就是這個設計的。挺獨特的。

      (5) 要想解決這一個問題,也是有辦法的,我經過兩天的鑽研,終於找到了方法,現分享給大家:就是在App1中啓動一個activity,這個activity的launcherMode=“standard”,然後由它來啓動App1Activity,這樣就避免了這一現象;

      啓動一個默認的activity:LoginApp1Activity代碼:

package com.cn.daming.app1;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class LoginApp1Activity extends Activity{

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Intent intent = (Intent)getIntent().clone();
		intent.setClass(LoginApp1Activity.this, App1Activity.class);
		startActivity(intent);
		finish();
	}
}

      

         manifest.xml中的代碼:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cn.daming.app1"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:launchMode="standard"
            android:alwaysRetainTaskState="true"
            android:name=".LoginApp1Activity" 
            >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:alwaysRetainTaskState="true"
            android:name=".App1Activity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.DELETE"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

     

 

      (6)從啓動App1---->點擊按鈕啓動App2---->點擊home鍵---->在點擊App1---->進入App2界面的log如下:


       說明:從mainmenu見面點擊App1的時候,調用的是App2Activiy的onRestart()方法---->onStart()---->onResume()方法,而不是調用的onDestroy()方法。

 

        原因梳理:

 在Task範圍內只產生一個實例。且這個實例啓動完成後是在棧頂。

如果在一個apk應用裏,singleTask的A啓動了一個B,B中啓動了A,這樣的情況怎麼辦呢?如果A是在原Task中啓  動,系統會將B結束,然後保持原A在棧頂。

設置了"singleTask"啓動模式的Activity的特點:

1. 設置了"singleTask"啓動模式的Activity,它在啓動的時候,會先在系統中查找屬性值affinity等於它的屬性值taskAffinity的任務存在;如果存在這樣的任務,它就會在這個任務中啓動(此後在根據第2條檢查),否則就會在新任務中啓動。因此,如果我們想要設置了"singleTask"啓動模式的Activity在新的任務中啓動,就要爲它設置一個獨立的taskAffinity屬性值。

2. 如果設置了"singleTask"啓動模式的Activity不是在新的任務中啓動時,它會在已有的任務中查看是否已經存在相應的Activity實例,如果存在,就會把位於這個Activity實例上面的Activity全部結束掉,即最終這個Activity實例會位於任務的Stack頂端中(官方文檔說同時也在root of Stack,Stack底端,存疑1)。

   真正的原因是singleTask的模式有個FLAG_ACTIVITY_BROUGHT_TO_FRONT,它纔是最後的罪魁禍首!

   官方文檔上的解釋如下:

    If, when starting the activity, there is already a task running that starts with this activity, then instead of starting a new instance the current task is brought to the front. The existing instance will receive a call to Activity.onNewIntent() with the new Intent that is being started, and with the Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set. This is a superset of the singleTop mode, where if there is already an instance of the activity being started at the top of the stack, it will receive the Intent as described there (without the FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set). See the Tasks and Back Stack document for more details about tasks.

    大致意思爲:當啓動一個activity的時候,如果這個task中有這個activity的實例,這個實例就會被放到當前的task的前面,這個存在的實例是通過onNewIntent()調用實現的。伴隨着這個activity的flag---->(FLAG_ACTIVITY_BROUGHT_TO_FRONT )會被設置,這是singleTop模式的超集,如果當這兒已經有個實例的activity在這個棧的頂部,這時候它不會設置這個flag(FLAG_ACTIVITY_BROUGHT_TO_FRONT )的值,詳解看tasks和棧說明,更清楚的瞭解這方面的知識!

發佈了88 篇原創文章 · 獲贊 25 · 訪問量 124萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章