TaskAffinity,兩個英文單詞的組合,直譯爲任務相關性,可以用來設置Activity任務棧任務相關性的一個屬性,這個屬性參數指出了Activity所希望進入的任務棧的名稱。
taskAffinity的特性結論:
- 具有同一相關性的 Activity 歸屬同一任務(從用戶的角度來看,則是歸屬同一“應用”)
- 任務的相關性由其根 Activity 的相關性確定。(文中有測試分析)
- 我們也可以爲Activity單獨指定TaskAffinity屬性值,來改變一個Activity所需要的任務棧,對一個應用中的Activity進行任務分組。(文中有測試分析)
- 如果未設置該屬性,則 Activity 會繼承爲應用設置的任務相關性(請參閱 元素的 taskAffinity 屬性)。應用默認相關性的名稱爲 元素所設置的軟件包名稱。
- TaskAffinity屬性一般跟singleTask模式或者跟allowTaskReparenting屬性結合使用,在其它情況下,沒有意義。(重點分析)
- 該屬性可以將不同應用中定義的Activity置於同一任務中。
- 將該屬性設置爲空字符串,可使指定 Activity 與任何任務均無親和關係。有點像singleInstance的模式。單獨使用一個任務棧,且其內只有指定 Activity的一個實例。
以上的結論條目有點多,但是重點是第5條,在實際使用場景中,我們也是重點分析第5條的結論。
TaskAffinity與singleTask結合使用
下面我們看一下簡單的測試代碼
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hym.launchmode">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:taskAffinity="com.hym.taskaffinity"
android:launchMode="standard" />
<activity
android:name=".ThirdActivity"
android:launchMode="singleTask"
android:taskAffinity="com.hym.taskaffinity2"/>
</application>
</manifest>
FirstActivity啓動SecondActivity,SecondActivity(launchmode=standard)啓動ThirdActivity(launchmode=singleTask)。這裏面省去了Activity中部分代碼,需要測試的可以自行編寫,比較簡單。那麼,三個Activity都啓動完成後,我們打開控制檯通過敲adb shell dumpsys activity activities回車,我們能看到當前的任務棧信息如下
通過這個實例說明兩點:
1、taskAffinity屬性結合singleTask啓動模式一起使用,會創建一個新的任務棧,給當前的這個Activity。
2、taskAffinity屬性結合stardard啓動模式一起使用,並沒有創建新的任務棧,而是默認存在於啓動這個Activity所在任務棧中。
allowTaskReparenting:
allowTaskReparenting,這個屬性是三個單詞的組合,允許更改父項:允許一個Activity迴歸其原始任務棧。什麼意思呢,其實這個屬性設置後,是允許了一個Activity在特定的情況下,可以進行任務棧切換。
我們仍然做一段測試,應用A中的FirstActivity代碼:
A應用中的FirstActivity.java
package com.hym.launchmode;
import android.content.ComponentName;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
//打開外部程序設置
intent.setComponent(new ComponentName("com.hym.laucher2","com.hym.laucher2.ReparentActivity"));
startActivity(intent);
}
});
}
}
應用2中的ReparentingActivity.java
package com.hym.laucher2;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class ReparentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reparent);
}
}
應用2中的manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hym.laucher2">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--需要設置允許能被外部引用調用-->
<activity android:name=".ReparentActivity"
android:exported="true"
android:allowTaskReparenting="true"></activity>
</application>
</manifest>
運行應用1啓動FirstActivity,然後點擊啓動應用2中的ReparentingActivity,然後我們執行shell腳本,adb shelldumpsys activity activities
然後點擊home鍵,回到桌面,然後再點擊應用2的icon後,執行查看腳本
從這兩張截圖,我們分析下,第一張圖,應用2的ReparentingActivity實例,實例的引用編號是269ccc,啓動後,進入到了id=730任務棧中,之後點擊home,回到桌面,點擊應用2的啓動Icon啓動後,我們看到了兩個任務棧,且原ReparentingActivity的實例,進入到了新的任務棧中,任務棧id=731。
此時我們把應用2中的ReparentingActivity配置修改一下android:allowTaskReparenting=“false”,在執行上述的交互操作,然後執行腳本查看任務棧情況:
通過對比我們發現,如果android:allowTaskReparenting="false"的時候,ReparentActivity的實例,並沒有回到應用2啓動後的任務棧中,應用2的任務棧中,只有應用2的laucher:FirstActivity。
從上圖中,第二行的TaskRecord A=com.hym.laucher2,第四行的TaskRecord A=com.hym.lanuchmode。其中“com.hym.laucher2”、“com.hym.lanuchmode”我們可以理解成任務棧的taskAffinity,任務棧的taskAffinity是由這個任務棧中的root activity決定的,比如任務棧“com.hym.lanuchmode”名字是由#0 …FirstActivity的任務棧決定的。
結論是:
- 對於standard標準模式的如果設置了android:allowTaskReparenting=“true”,在特定的情況下,可以支持Activity的切換。(singleTop同standard的結論)
- 一個任務棧的root Activity總是擁有和它所在的任務棧具有相同的Activity。
- 由於以singleTask和singleInstance啓動的Activity只能和任務棧的taskAffinity是一致的,所以屬性allowTaskReparenting在這兩種模式下是無效的。
TaskAffinity和allowTaskReparenting使用場景:
看一下場景:一個e-mail(A應用)應用消息頁面中包含一個網頁鏈接,點擊瀏覽器應用程序c-Activity顯示這個頁面,雖然這個Activity是瀏覽器應用(B應用)定義的,但是activity是由email應用程序調用加載的,所以這個時候該activity屬於e-mail的任務棧task。如果e-mail應用切換到後臺,瀏覽器在下次打開時,如果這個Activity的allowTaskReparenting屬性值是true,此時瀏覽器就會顯示該activity而不顯示瀏覽器主界面,同時,activity也將從e-mail的任務棧遷移到瀏覽器的任務棧,下次打開e-mail時,並不會顯示該Activity。
配合一張簡單的示意圖幫助理解,A應用爲郵件應用,B應用爲瀏覽器應用。
上述場景描述來自於網絡,如果我們在實際開發的過程中,需要達成類似上述場景的交互形式,我們就可以用allowTaskReparenting屬性設置來達成。
終極結論:taskAffinity的使用場景,大多被用於兩個應用之間的頁面交互,在日常的應用開發中,使用並不是很多。有興趣的可以去體驗一些大廠開發的能夠被三方調用的一些app交互,比如抖音、微博、微信、淘寶等,來體驗使用場景。