不同点 | DeepLink | AppLink |
---|---|---|
Intent scheme | 任意 | 要求http或https |
Intent action | 任意Action | 要求配置andorid.intent.action.VIEW |
Intent category | 任意Category | 要求配置android.intent.category.BROWSABLE和android.intent.category.DEFAULT |
链接认证 | 无需验证 | 要求进行Digital Asset Links文件验证 |
用户体验 | 可能展示一个多选项弹窗或确认弹窗,用户需要二次选择或确认 | 无弹窗,直接由App处理链接 |
兼容性 | 所有版本 | Android6.0及以上版本 |
<!--用于DeepLink,html跳到此页面 scheme_Adr: 'yilu://link/?page=main',-->
<activity android:name=".activity.link.SchemeActivity"
android:screenOrientation="portrait">
<!--Android 接收外部跳转过滤器-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- 协议部分配置 ,要在web配置相同的-->
<!--yilu://link/?page=main-->
<data
android:host="link"
android:scheme="yilu" />
</intent-filter>
</activity>
//解析数据
@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();
//获取指定参数值
String page = uri.getQueryParameter("page");
switch (page) {
case "main":
//唤起客户端,进入首页
//https://yc.com?page=main
Intent intent1 = new Intent(this, MainActivity.class);
readGoActivity(intent1, this);
break;
case "full":
//唤起客户端,进入A页面
//https://yc.com?page=full
Intent intent2 = new Intent(this, TestFullActivity.class);
readGoActivity(intent2, this);
break;
case "list":
//唤起客户端,进入B页面,携带参数
//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;
}
}
Intent intent=new Intent();
intent.setData(Uri.parse("yilu://link/?page=main"));
startActivity(intent);
public void readGoActivity(Intent intent, Context context) {
// 如果app 运行中,直接打开页面,没有运行中就先打开主界面,在打开
if (isAppRunning(context, context.getPackageName())) {
openActivity(intent, context);
} else {
//先打开首页,然后跳转指定页面
reStartActivity(intent, context);
}
}
public void openActivity(Intent intent, Context context) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
* 注意,为何要这样跳转,首先需要先跳转首页,然后在跳转到指定页面,那么回来的时候始终是首页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);
}
<activity android:name=".SchemeActivity"
android:screenOrientation="portrait">
<!--Android 接收外部跳转过滤器-->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="yc.com"/>
</intent-filter>
</activity>
{
relation: [
"delegate_permission/common.handle_all_urls"
],
target: {
namespace: "android_app",
package_name: "com.yc.video",
sha256_cert_fingerprints: [
"4D:8A:27:58:E2:00:2E:0B:E2:46:54:74:7D:3E:F2:27:CE:46:FE:08:8D:CF:F7:34:54:B8:36:6D:7B:32:58:A0"
]
}
}
<html>
<body>
<a href="yilu://link/?page=main">立即打开一鹿报价页面(直接打开)>></a>
</body>
</html>
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
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)
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;
//创建新的Intent对象,即便intent被修改也不受影响
intent = new Intent(intent);
//收集Intent所指向的Activity信息, 当存在多个可供选择的Activity,则直接向用户弹出resolveActivity
ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
...
}
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;
}
@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);
}
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();
}
<!--如果用户已经选择过默认打开的APP,则这里返回的就是相对应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) {
...
<!--弄一个ResolveActivity的ActivityInfo-->
if (mResolveComponentName.equals(component)) {
return PackageParser.generateActivityInfo(mResolveActivity, flags,
new PackageUserState(), userId);
}
}
return null;
}
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)));
}
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
final int installFlags = args.installFlags;
<!--开始验证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);
//重点看这里,发送了一个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);
}
//零碎代码,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设置了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是否设置autoverify -->
if (filter.needsVerification() && needsNetworkVerificationLPr(filter)) {
needToVerify = true;
break;
}
}
}
<!--如果有搜集需要验证的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++;
} } } } }
<!--开始验证-->
if (count > 0) {
mIntentFilterVerifier.startVerifications(userId);
}
}
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);
// 必须设置Intent.ACTION_VIEW 必须设置有ACTION_DEFAULT 必须要有SCHEME_HTTPS或者SCHEME_HTTP,查到一个就可以
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;
}
public final boolean needsVerification() {
return getAutoVerify() && handlesWebUris(true);
}
public final boolean getAutoVerify() {
return ((mVerifyState & STATE_VERIFY_AUTO) == STATE_VERIFY_AUTO);
}
<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>
@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);
}
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);
}
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;
}
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 {
..
}
}
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);
}
<!--通过网络请求获取配置-->
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());
<!--如果有一对多的情况,或者说设置了“代理”,则循环获取配置-->
for (String delegate : result.getDelegates()) {
statements.addAll(
retrieveStatementFromUrl(delegate, maxIncludeLevel - 1, source)
.getStatements());
}
<!--发送结果-->
return Result.create(statements, webContent.getExpireTimeMillis());
} catch (JSONException | IOException e) {
return Result.create(statements, DO_NOT_CACHE_RESULT);
}
}
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);
}