IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android deeplink原理解析 -> 正文阅读

[移动开发]Android deeplink原理解析

一、什么是DeepLink

移动端深度链接,简称deeplink。这是一种通过uri链接到app特定位置的一种跳转技术,不单是简单地通过网页、app等打开目标app,还能达到利用传递标识跳转至不同页面的效果。

二、原理分析

deeplink的scheme相应分两种:一种是只有一个APP能相应,另一种是有多个APP可以相应,比如,如果为一个APP的Activity配置了http scheme类型的deepLink,如果通过短信或者其他方式唤起这种link的时候,一般会出现一个让用户选择的弹窗,因为一般而言,系统会带个浏览器,也相应这类scheme。当然,如果私有scheme跟其他APP的重复了,还是会唤起APP选择界面。下面就来看看scheme是如何匹配并拉起对应APP的。

DeepLink其实都是通过唤起一个Activity来实现界面的跳转,无论从APP外部:比如短信、浏览器,还是APP内部。通过在APP内部模拟跳转来看看具体实现,写一个H5界面,然后通过Webview加载,不过Webview不进行任何设置,这样跳转就需要系统进行解析,走deeplink这一套:

<html>
<body> 
    <a href="yilu://link/?page=main">立即打开一鹿报价页面(直接打开)&gt;&gt;</a>
</body>
</html>

点击Scheme跳转,一般会唤起如下界面,让用户选择打开方式

通过adb打印log,你会发现ActivityManagerService会打印这样一条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

其实看到的选择对话框就是ResolverActivity

不过我们先来看看到底是走到ResolverActivity的,也就是这个scheme怎么会唤起App选择界面,在短信中,或者Webview中遇到scheme,他们一般会发出相应的Intent(当然第三方APP可能会屏蔽掉,比如微信就换不起APP),其实上面的作用跟下面的代码结果一样

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最后会通过ActivityManagerService调用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;
    //创建新的Intent对象,即便intent被修改也不受影响
    intent = new Intent(intent);
     //收集Intent所指向的Activity信息, 当存在多个可供选择的Activity,则直接向用户弹出resolveActivity 
    ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
    ...
    
    }

startActivityMayWait会通过resolveActivity先找到目标Activity,这个过程中,可能找到多个匹配的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;
    }

可以认为,所有的四大组件的信息都在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会通过queryIntentActivities找到所有适合的Activity,再通过

chooseBestActivity提供选择的权利。这里分如下三种情况:

  • 仅仅找到一个,直接启动
  • 找到了多个,并且设置了其中一个为默认启动,则直接启动相应Acitivity
  • 找到了多个,切没有设置默认启动,则启动ResolveActivity供用户选择

关于如何查询,匹配的这里不详述,仅仅简单看看如何唤起选择页面,或者默认打开,比较关键的就是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();
        }
         
        <!--如果用户已经选择过默认打开的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) {
        ...
        <!--弄一个ResolveActivityActivityInfo-->
        if (mResolveComponentName.equals(component)) {
            return PackageParser.generateActivityInfo(mResolveActivity, flags,
                    new PackageUserState(), userId);
        }
    }
    return null;
}

其实上述流程比较复杂,这里只是自己简单猜想下流程,找到目标Activity后,无论是真的目标Acitiviy,还是ResolveActivity,都会通过startActivityLocked继续走启动流程,这里就会看到之前打印的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还会根据用户选择的信息将一些设置持久化到本地,这样下次就可以直接启动用户的偏好App。其实以上就是deeplink的原理,说白了一句话:scheme就是隐式启动Activity,如果能找到唯一或者设置的目标Acitivity则直接启动,如果找到多个,则提供APP选择界面。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-06 13:56:30  更:2022-02-06 13:58:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 14:45:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码