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选择界面。

### 部署 Stable Diffusion 的准备工作 为了成功部署 Stable Diffusion,在本地环境中需完成几个关键准备事项。确保安装了 Python 和 Git 工具,因为这些对于获取源码和管理依赖项至关重要。 #### 安装必要的软件包和支持库 建议创建一个新的虚拟环境来隔离项目的依赖关系。这可以通过 Anaconda 或者 venv 实现: ```bash conda create -n sd python=3.9 conda activate sd ``` 或者使用 `venv`: ```bash python -m venv sd-env source sd-env/bin/activate # Unix or macOS sd-env\Scripts\activate # Windows ``` ### 下载预训练模型 Stable Diffusion 要求有预先训练好的模型权重文件以便能够正常工作。可以从官方资源或者其他可信赖的地方获得这些权重文件[^2]。 ### 获取并配置项目代码 接着要做的就是把最新的 Stable Diffusion WebUI 版本拉取下来。在命令行工具里执行如下指令可以实现这一点;这里假设目标路径为桌面下的特定位置[^3]: ```bash git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git ~/Desktop/stable-diffusion-webui cd ~/Desktop/stable-diffusion-webui ``` ### 设置 GPU 支持 (如果适用) 当打算利用 NVIDIA 显卡加速推理速度时,则需要确认 PyTorch 及 CUDA 是否已经正确设置好。下面这段简单的测试脚本可以帮助验证这一情况[^4]: ```python import torch print(f"Torch version: {torch.__version__}") if torch.cuda.is_available(): print("CUDA is available!") else: print("No CUDA detected.") ``` 一旦上述步骤都顺利完成之后,就可以按照具体文档中的指导进一步操作,比如调整参数、启动服务端口等等。整个过程中遇到任何疑问都可以查阅相关资料或社区支持寻求帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网小熊猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值