- 1.指定scheme跳轉(zhuǎn)規(guī)則,關(guān)于scheme的協(xié)議規(guī)則,這里不作過多解釋,[scheme]://[host]/[path]?[query]。比如暫時(shí)是這樣設(shè)定的:yilu://link/?page=main。
- 2.被喚起方,客戶端需要配置清單文件activity。關(guān)于SchemeActivity注意查看下面代碼:
- 為什么要配置intent-filter,它是針對(duì)你跳轉(zhuǎn)的目標(biāo)來講的,比如你要去某個(gè)朋友的家,就類似于門牌的修飾,他會(huì)在門牌上定義上述介紹的那些屬性,方便你定位。當(dāng)有intent發(fā)送過來的時(shí)候,就會(huì)篩選出符合條件的app來。
- action.VIEW是打開一個(gè)視圖,在Android 系統(tǒng)中點(diǎn)擊鏈接會(huì)發(fā)送一條action=VIEW的隱式意圖,這個(gè)必須配置。
- category.DEFAULT為默認(rèn),category.DEFAULT為設(shè)置該組件可以使用瀏覽器啟動(dòng),這個(gè)是關(guān)鍵,從瀏覽器跳轉(zhuǎn),就要通過這個(gè)屬性。
<!--用于DeepLink,html跳到此頁面 scheme_Adr: 'yilu://link/?page=main',-->
<activity android:name=".activity.link.SchemeActivity"
android:screenOrientation="portrait">
<!--Android 接收外部跳轉(zhuǎn)過濾器-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- 協(xié)議部分配置 ,要在web配置相同的-->
<!--yilu://link/?page=main-->
<data
android:host="link"
android:scheme="yilu" />
</intent-filter>
</activity>
//解析數(shù)據(jù)
@Override
public void onCreate(Bundle savesInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent=getIntent();
String action=intent.getAction();
Uri data=intent.getData();
//解析data
String scheme=data.getScheme();
String host=data.getHost();
String path=data.getPath();
int port=data.getPort();
Set<String> paramKeySet=data.getQueryParameterNames();
//獲取指定參數(shù)值
String page = uri.getQueryParameter("page");
switch (page) {
case "main":
//喚起客戶端,進(jìn)入首頁
//https://yc.com?page=main
Intent intent1 = new Intent(this, MainActivity.class);
readGoActivity(intent1, this);
break;
case "full":
//喚起客戶端,進(jìn)入A頁面
//https://yc.com?page=full
Intent intent2 = new Intent(this, TestFullActivity.class);
readGoActivity(intent2, this);
break;
case "list":
//喚起客戶端,進(jìn)入B頁面,攜帶參數(shù)
//https://yc.com?page=list&id=520
Intent intent3 = new Intent(this, TestListActivity.class);
String id = getValueByName(url, "id");
intent3.putExtra("id",id);
readGoActivity(intent3, this);
break;
default:
Intent intent = new Intent(this, MainActivity.class);
readGoActivity(intent, this);
break;
}
}
- 3.喚起方也需要操作
Intent intent=new Intent();
intent.setData(Uri.parse("yilu://link/?page=main"));
startActivity(intent);
- 4.關(guān)于問題疑惑點(diǎn)解決方案
- 配置了scheme協(xié)議,測(cè)試可以打開app,但是想跳到具體頁面,攜帶參數(shù),又該如何實(shí)現(xiàn)呢?
- 比如則可以配置:yilu://link/?page=car&id=520,則可以跳轉(zhuǎn)到汽車詳情頁面,然后傳遞的id參數(shù)是520。
- 5.跳轉(zhuǎn)頁面后的優(yōu)化
- 通過以上規(guī)則匹配上,你點(diǎn)擊跳轉(zhuǎn)以后,如果用戶結(jié)束這個(gè)Activity的話,就直接回到桌面了,這個(gè)是比較奇怪的。參考一些其他app,發(fā)現(xiàn)不管是跳轉(zhuǎn)指定的幾級(jí)頁面,點(diǎn)擊返回是回到首頁,那么這個(gè)是如何做到的呢?代碼如下所示
public void readGoActivity(Intent intent, Context context) {
// 如果app 運(yùn)行中,直接打開頁面,沒有運(yùn)行中就先打開主界面,在打開
if (isAppRunning(context, context.getPackageName())) {
openActivity(intent, context);
} else {
//先打開首頁,然后跳轉(zhuǎn)指定頁面
reStartActivity(intent, context);
}
}
public void openActivity(Intent intent, Context context) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
* 注意,為何要這樣跳轉(zhuǎn),首先需要先跳轉(zhuǎn)首頁,然后在跳轉(zhuǎn)到指定頁面,那么回來的時(shí)候始終是首頁Main頁面
* @param intent intent
* @param context 上下文
*/
public void reStartActivity(Intent intent, Context context) {
Intent[] intents = new Intent[2];
Intent mainIntent = new Intent(context, MainActivity.class);
mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intents[0] = mainIntent;
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intents[1] = intent;
context.startActivities(intents);
}
- 6.短信息竟無法識(shí)別scheme協(xié)議?
- 把yilu://link/?page=main以短信息發(fā)送出去,然后在短信息里點(diǎn)擊鏈接,發(fā)現(xiàn)在短信里面添加的鏈接自定義的scheme被認(rèn)為不是一個(gè)scheme……可見終究跳不開的http/https訪問。
- 7.如何將一個(gè)http或https鏈接生成短鏈接
- 這個(gè)很容易,直接找個(gè)短鏈接生成的網(wǎng)站,然后把鏈接轉(zhuǎn)化一下就可以。至于轉(zhuǎn)化的原理,我暫時(shí)也不清楚……
- deeplink的scheme相應(yīng)分兩種:一種是只有一個(gè)APP能相應(yīng),另一種是有多個(gè)APP可以相應(yīng),比如,如果為一個(gè)APP的Activity配置了http scheme類型的deepLink,如果通過短信或者其他方式喚起這種link的時(shí)候,一般會(huì)出現(xiàn)一個(gè)讓用戶選擇的彈窗,因?yàn)橐话愣裕到y(tǒng)會(huì)帶個(gè)瀏覽器,也相應(yīng)這類scheme。這里就不舉例子了,因?yàn)樯厦嬉呀?jīng)已經(jīng)提到呢。當(dāng)然,如果私有scheme跟其他APP的重復(fù)了,還是會(huì)喚起APP選擇界面(其實(shí)是一個(gè)ResolverActivity)。下面就來看看scheme是如何匹配并拉起對(duì)應(yīng)APP的。
- startActivity入口與ResolverActivity
- 無論APPLink跟DeepLink其實(shí)都是通過喚起一個(gè)Activity來實(shí)現(xiàn)界面的跳轉(zhuǎn),無論從APP外部:比如短信、瀏覽器,還是APP內(nèi)部。通過在APP內(nèi)部模擬跳轉(zhuǎn)來看看具體實(shí)現(xiàn),寫一個(gè)H5界面,然后通過Webview加載,不過Webview不進(jìn)行任何設(shè)置,這樣跳轉(zhuǎn)就需要系統(tǒng)進(jìn)行解析,走deeplink這一套:
<html>
<body>
<a href="yilu://link/?page=main">立即打開一鹿報(bào)價(jià)頁面(直接打開)>></a>
</body>
</html>
- 點(diǎn)擊Scheme跳轉(zhuǎn),一般會(huì)喚起如下界面,讓用戶選擇打開方式:
- 通過adb打印log,你會(huì)發(fā)現(xiàn)ActivityManagerService會(huì)打印這樣一條Log:
ActivityManager: START u0 {act=android.intent.action.VIEW dat=yilu://link/... cmp=android/com.android.internal.app.ResolverActivity (has extras)} from uid 10067 on display 0
- 其實(shí)看到的選擇對(duì)話框就是ResolverActivity
- 不過我們先來看看到底是走到ResolverActivity的,也就是這個(gè)scheme怎么會(huì)喚起App選擇界面,在短信中,或者Webview中遇到scheme,他們一般會(huì)發(fā)出相應(yīng)的Intent(當(dāng)然第三方APP可能會(huì)屏蔽掉,比如微信就換不起APP),其實(shí)上面的作用跟下面的代碼結(jié)果一樣:
Intent intent = new Intent()
intent.setAction("android.intent.action.VIEW")
intent.setData(Uri.parse("https://yc.com/history/520"))
intent.addCategory("android.intent.category.DEFAULT")
intent.addCategory("android.intent.category.BROWSABLE")
startActivity(intent)
- 那剩下的就是看startActivity,在源碼中,startActivity最后會(huì)通過ActivityManagerService調(diào)用ActivityStatckSupervisor的startActivityMayWait
final int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, Configuration config, Bundle options, boolean ignoreTargetSecurity, int userId, IActivityContainer iContainer, TaskRecord inTask) {
...
boolean componentSpecified = intent.getComponent() != null;
//創(chuàng)建新的Intent對(duì)象,即便intent被修改也不受影響
intent = new Intent(intent);
//收集Intent所指向的Activity信息, 當(dāng)存在多個(gè)可供選擇的Activity,則直接向用戶彈出resolveActivity
ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
...
}
- startActivityMayWait會(huì)通過resolveActivity先找到目標(biāo)Activity,這個(gè)過程中,可能找到多個(gè)匹配的Activity,這就是ResolverActivity的入口:
ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
ProfilerInfo profilerInfo, int userId) {
// Collect information about the target of the Intent.
ActivityInfo aInfo;
try {
ResolveInfo rInfo =
AppGlobals.getPackageManager().resolveIntent(
intent, resolvedType,
PackageManager.MATCH_DEFAULT_ONLY
| ActivityManagerService.STOCK_PM_FLAGS, userId);
aInfo = rInfo != null ? rInfo.activityInfo : null;
} catch (RemoteException e) {
aInfo = null;
}
- 可以認(rèn)為,所有的四大組件的信息都在PackageManagerService中有登記,想要找到這些類,就必須向PackagemanagerService查詢
@Override
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "resolve intent");
List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);
return chooseBestActivity(intent, resolvedType, flags, query, userId);
}
- PackageManagerService會(huì)通過queryIntentActivities找到所有適合的Activity,再通過chooseBestActivity提供選擇的權(quán)利。這里分如下三種情況:
- 僅僅找到一個(gè),直接啟動(dòng)
- 找到了多個(gè),并且設(shè)置了其中一個(gè)為默認(rèn)啟動(dòng),則直接啟動(dòng)相應(yīng)Acitivity
- 找到了多個(gè),切沒有設(shè)置默認(rèn)啟動(dòng),則啟動(dòng)ResolveActivity供用戶選擇
- 關(guān)于如何查詢,匹配的這里不詳述,僅僅簡(jiǎn)單看看如何喚起選擇頁面,或者默認(rèn)打開,比較關(guān)鍵的就是chooseBestActivity
private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
int flags, List<ResolveInfo> query, int userId) {
<!--查詢最好的Activity-->
ResolveInfo ri = findPreferredActivity(intent, resolvedType,
flags, query, r0.priority, true, false, debug, userId);
if (ri != null) {
return ri;
}
...
}
ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags,
List<ResolveInfo> query, int priority, boolean always,
boolean removeMatches, boolean debug, int userId) {
if (!sUserManager.exists(userId)) return null;
// writer
synchronized (mPackages) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
}
<!--如果用戶已經(jīng)選擇過默認(rèn)打開的APP,則這里返回的就是相對(duì)應(yīng)APP中的Activity-->
ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
debug, userId);
if (pri != null) {
return pri;
}
<!--找Activity-->
PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId);
...
final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent,
flags | PackageManager.GET_DISABLED_COMPONENTS, userId);
...
}
@Override
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get activity info");
synchronized (mPackages) {
...
<!--弄一個(gè)ResolveActivity的ActivityInfo-->
if (mResolveComponentName.equals(component)) {
return PackageParser.generateActivityInfo(mResolveActivity, flags,
new PackageUserState(), userId);
}
}
return null;
}
- 其實(shí)上述流程比較復(fù)雜,這里只是自己簡(jiǎn)單猜想下流程,找到目標(biāo)Activity后,無論是真的目標(biāo)Acitiviy,還是ResolveActivity,都會(huì)通過startActivityLocked繼續(xù)走啟動(dòng)流程,這里就會(huì)看到之前打印的Log信息:
final int startActivityLocked(IApplicationThread caller...{
if (err == ActivityManager.START_SUCCESS) {
Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
+ "} from uid " + callingUid
+ " on display " + (container == null ? (mFocusedStack == null ?
Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) :
(container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
container.mActivityDisplay.mDisplayId)));
}
- 如果是ResolveActivity還會(huì)根據(jù)用戶選擇的信息將一些設(shè)置持久化到本地,這樣下次就可以直接啟動(dòng)用戶的偏好App。其實(shí)以上就是deeplink的原理,說白了一句話:scheme就是隱式啟動(dòng)Activity,如果能找到唯一或者設(shè)置的目標(biāo)Acitivity則直接啟動(dòng),如果找到多個(gè),則提供APP選擇界面。
- 之前分析deeplink的時(shí)候提到了ResolveActivity這么一個(gè)選擇過程,而AppLink就是自動(dòng)幫用戶完成這個(gè)選擇過程,并且選擇的scheme是最適合它的scheme(開發(fā)者的角度)。因此對(duì)于AppLink要分析的就是如何完成了這個(gè)默認(rèn)選擇的過程。
- 目前Android源碼提供的是一個(gè)雙向認(rèn)證的方案:在APP安裝的時(shí)候,客戶端根據(jù)APP配置像服務(wù)端請(qǐng)求,如果滿足條件,scheme跟服務(wù)端配置匹配的上,就為APP設(shè)置默認(rèn)啟動(dòng)選項(xiàng),所以這個(gè)方案很明顯,在安裝的時(shí)候需要聯(lián)網(wǎng)才行,否則就是完全不會(huì)驗(yàn)證,那就是普通的deeplink,既然是在安裝的時(shí)候去驗(yàn)證,那就看看PackageManagerService是如何處理這個(gè)流程的,具體找到installPackageLI方法:
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
final int installFlags = args.installFlags;
<!--開始驗(yàn)證applink-->
startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
...
}
private void startIntentFilterVerifications(int userId, boolean replacing,
PackageParser.Package pkg) {
if (mIntentFilterVerifierComponent == null) {
return;
}
final int verifierUid = getPackageUid(
mIntentFilterVerifierComponent.getPackageName(),
(userId == UserHandle.USER_ALL) ? UserHandle.USER_OWNER : userId);
//重點(diǎn)看這里,發(fā)送了一個(gè)handler消息
mHandler.removeMessages(START_INTENT_FILTER_VERIFICATIONS);
final Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS);
msg.obj = new IFVerificationParams(pkg, replacing, userId, verifierUid);
mHandler.sendMessage(msg);
}
- 可以看到發(fā)送了一個(gè)handler消息,那么消息里做了什么呢?看一下startIntentFilterVerifications發(fā)送一個(gè)消息開啟驗(yàn)證,隨后調(diào)用verifyIntentFiltersIfNeeded進(jìn)行驗(yàn)證,代碼如下所示:
- 以看出,驗(yàn)證就三步:檢查、搜集、驗(yàn)證。在檢查階段,首先看看是否有設(shè)置http/https scheme的Activity,并且是否滿足設(shè)置了Intent.ACTION_DEFAULT與Intent.ACTION_VIEW,如果沒有,則壓根不需要驗(yàn)證
//零碎代碼,handler接受消息的地方代碼
case START_INTENT_FILTER_VERIFICATIONS: {
IFVerificationParams params = (IFVerificationParams) msg.obj;
verifyIntentFiltersIfNeeded(params.userId, params.verifierUid,
params.replacing, params.pkg);
break;
}
//verifyIntentFiltersIfNeeded方法
private void verifyIntentFiltersIfNeeded(int userId, int verifierUid, boolean replacing,
PackageParser.Package pkg) {
...
<!--檢查是否有Activity設(shè)置了AppLink-->
final boolean hasDomainURLs = hasDomainURLs(pkg);
if (!hasDomainURLs) {
if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
"No domain URLs, so no need to verify any IntentFilter!");
return;
}
<!--是否autoverigy-->
boolean needToVerify = false;
for (PackageParser.Activity a : pkg.activities) {
for (ActivityIntentInfo filter : a.intents) {
<!--needsVerification是否設(shè)置autoverify -->
if (filter.needsVerification() && needsNetworkVerificationLPr(filter)) {
needToVerify = true;
break;
}
}
}
<!--如果有搜集需要驗(yàn)證的Activity信息及scheme信息-->
if (needToVerify) {
final int verificationId = mIntentFilterVerificationToken++;
for (PackageParser.Activity a : pkg.activities) {
for (ActivityIntentInfo filter : a.intents) {
if (filter.handlesWebUris(true) && needsNetworkVerificationLPr(filter)) {
if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
"Verification needed for IntentFilter:" + filter.toString());
mIntentFilterVerifier.addOneIntentFilterVerification(
verifierUid, userId, verificationId, filter, packageName);
count++;
} } } } }
<!--開始驗(yàn)證-->
if (count > 0) {
mIntentFilterVerifier.startVerifications(userId);
}
}
- 具體看一下hasDomainURLs到底做了什么?
private static boolean hasDomainURLs(Package pkg) {
if (pkg == null || pkg.activities == null) return false;
final ArrayList<Activity> activities = pkg.activities;
final int countActivities = activities.size();
for (int n=0; n<countActivities; n++) {
Activity activity = activities.get(n);
ArrayList<ActivityIntentInfo> filters = activity.intents;
if (filters == null) continue;
final int countFilters = filters.size();
for (int m=0; m<countFilters; m++) {
ActivityIntentInfo aii = filters.get(m);
// 必須設(shè)置Intent.ACTION_VIEW 必須設(shè)置有ACTION_DEFAULT 必須要有SCHEME_HTTPS或者SCHEME_HTTP,查到一個(gè)就可以
if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue;
if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
return true;
}
}
}
return false;
}
- 檢查的第二步試看看是否設(shè)置了autoverify,當(dāng)然中間還有些是否設(shè)置過,用戶是否選擇過的操作,比較復(fù)雜,不分析,不過不影響對(duì)流程的理解:
public final boolean needsVerification() {
return getAutoVerify() && handlesWebUris(true);
}
public final boolean getAutoVerify() {
return ((mVerifyState & STATE_VERIFY_AUTO) == STATE_VERIFY_AUTO);
}
- 只要找到一個(gè)滿足以上條件的Activity,就開始驗(yàn)證。如果想要開啟applink,Manifest中配置必須像下面這樣
<intent-filter android:autoVerify="true">
<data android:scheme="https" android:host="xxx.com" />
<data android:scheme="http" android:host="xxx.com" />
<!--外部intent打開,比如短信,文本編輯等-->
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- 搜集其實(shí)就是搜集intentfilter信息,下面直接看驗(yàn)證過程
@Override
public void startVerifications(int userId) {
...
sendVerificationRequest(userId, verificationId, ivs);
}
mCurrentIntentFilterVerifications.clear();
}
private void sendVerificationRequest(int userId, int verificationId,
IntentFilterVerificationState ivs) {
Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
verificationId);
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
getDefaultScheme());
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
ivs.getHostsString());
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
ivs.getPackageName());
verificationIntent.setComponent(mIntentFilterVerifierComponent);
verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
UserHandle user = new UserHandle(userId);
mContext.sendBroadcastAsUser(verificationIntent, user);
}
- 目前Android的實(shí)現(xiàn)是通過發(fā)送一個(gè)廣播來進(jìn)行驗(yàn)證的,也就是說,這是個(gè)異步的過程,驗(yàn)證是需要耗時(shí)的(網(wǎng)絡(luò)請(qǐng)求),所以安裝后,一般要等個(gè)幾秒Applink才能生效,廣播的接受處理者是:IntentFilterVerificationReceiver
public final class IntentFilterVerificationReceiver extends BroadcastReceiver {
private static final String TAG = IntentFilterVerificationReceiver.class.getSimpleName();
...
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION.equals(action)) {
Bundle inputExtras = intent.getExtras();
if (inputExtras != null) {
Intent serviceIntent = new Intent(context, DirectStatementService.class);
serviceIntent.setAction(DirectStatementService.CHECK_ALL_ACTION);
...
serviceIntent.putExtras(extras);
context.startService(serviceIntent);
}
- IntentFilterVerificationReceiver收到驗(yàn)證消息后,通過start一個(gè)DirectStatementService進(jìn)行驗(yàn)證,兜兜轉(zhuǎn)轉(zhuǎn)最終調(diào)用IsAssociatedCallable的verifyOneSource
private class IsAssociatedCallable implements Callable<Void> {
private boolean verifyOneSource(AbstractAsset source, AbstractAssetMatcher target,
Relation relation) throws AssociationServiceException {
Result statements = mStatementRetriever.retrieveStatements(source);
for (Statement statement : statements.getStatements()) {
if (relation.matches(statement.getRelation())
&& target.matches(statement.getTarget())) {
return true;
}
}
return false;
}
- IsAssociatedCallable會(huì)逐一對(duì)需要驗(yàn)證的intentfilter進(jìn)行驗(yàn)證,具體是通過DirectStatementRetriever的retrieveStatements來實(shí)現(xiàn):
Override
public Result retrieveStatements(AbstractAsset source) throws AssociationServiceException {
if (source instanceof AndroidAppAsset) {
return retrieveFromAndroid((AndroidAppAsset) source);
} else if (source instanceof WebAsset) {
return retrieveFromWeb((WebAsset) source);
} else {
..
}
}
- AndroidAppAsset好像是Google的另一套assetlink類的東西,好像用在APP web登陸信息共享之類的地方 ,不看,直接看retrieveFromWeb:從名字就能看出,這是獲取服務(wù)端Applink的配置,獲取后跟本地校驗(yàn),如果通過了,那就是applink啟動(dòng)成功:
private Result retrieveStatementFromUrl(String urlString, int maxIncludeLevel,
AbstractAsset source)
throws AssociationServiceException {
List<Statement> statements = new ArrayList<Statement>();
if (maxIncludeLevel < 0) {
return Result.create(statements, DO_NOT_CACHE_RESULT);
}
WebContent webContent;
try {
URL url = new URL(urlString);
if (!source.followInsecureInclude()
&& !url.getProtocol().toLowerCase().equals("https")) {
return Result.create(statements, DO_NOT_CACHE_RESULT);
}
<!--通過網(wǎng)絡(luò)請(qǐng)求獲取配置-->
webContent = mUrlFetcher.getWebContentFromUrlWithRetry(url,
HTTP_CONTENT_SIZE_LIMIT_IN_BYTES, HTTP_CONNECTION_TIMEOUT_MILLIS,
HTTP_CONNECTION_BACKOFF_MILLIS, HTTP_CONNECTION_RETRY);
} catch (IOException | InterruptedException e) {
return Result.create(statements, DO_NOT_CACHE_RESULT);
}
try {
ParsedStatement result = StatementParser
.parseStatementList(webContent.getContent(), source);
statements.addAll(result.getStatements());
<!--如果有一對(duì)多的情況,或者說設(shè)置了“代理”,則循環(huán)獲取配置-->
for (String delegate : result.getDelegates()) {
statements.addAll(
retrieveStatementFromUrl(delegate, maxIncludeLevel - 1, source)
.getStatements());
}
<!--發(fā)送結(jié)果-->
return Result.create(statements, webContent.getExpireTimeMillis());
} catch (JSONException | IOException e) {
return Result.create(statements, DO_NOT_CACHE_RESULT);
}
}
- 其實(shí)就是通過UrlFetcher獲取服務(wù)端配置,然后發(fā)給之前的receiver進(jìn)行驗(yàn)證:
public WebContent getWebContentFromUrl(URL url, long fileSizeLimit, int connectionTimeoutMillis)
throws AssociationServiceException, IOException {
final String scheme = url.getProtocol().toLowerCase(Locale.US);
if (!scheme.equals("http") && !scheme.equals("https")) {
throw new IllegalArgumentException("The url protocol should be on http or https.");
}
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(true);
connection.setConnectTimeout(connectionTimeoutMillis);
connection.setReadTimeout(connectionTimeoutMillis);
connection.setUseCaches(true);
connection.setInstanceFollowRedirects(false);
connection.addRequestProperty("Cache-Control", "max-stale=60");
...
return new WebContent(inputStreamToString(
connection.getInputStream(), connection.getContentLength(), fileSizeLimit),
expireTimeMillis);
}
- 看到這里的HttpURLConnection就知道為什么Applink需在安裝時(shí)聯(lián)網(wǎng)才有效,到這里其實(shí)就可以理解的差不多,后面其實(shí)就是針對(duì)配置跟App自身的配置進(jìn)行校驗(yàn),如果通過就設(shè)置默認(rèn)啟動(dòng),并持久化,驗(yàn)證成功的話可以通過。