Implementation principle of Android DeepLink

Posted by capbiker on Sun, 01 Mar 2020 09:30:20 +0100

Preface

Before that, we looked at the source code and studied animation. Today, we share a relatively simple technical point: DeepLink.

DeepLink, deep link technology, the main application scenario is to call Android native app directly through Web page, and pass the required parameters to app directly in the form of Uri, saving the user's registration cost. After briefly introducing the concept of DeepLink, let's look at a practical example:

My friend shared a shopping link with me through Jingdong:

So I opened this link through wechat:

Open this URL link in wechat and prompt me to open JD app. If I click allow, I will open JD app in my mobile phone and jump to the details page of this product:

In this way, I can quickly find the products I need to view and complete the purchase related operations. Is it very convenient? This is DeepLink.

text

Is it very difficult for such a vicious DeepLink? In fact, the basic implementation of DeepLink is incredibly simple, and its core idea is actually the implicit startup of Android. Our usual implicit startup is to start the specified type of Activity through the cooperation of Action and Category:

<activity
      android:name=".SecondActivity"
      android:exported="true">
      <intent-filter>
           <action android:name="com.lzp.deeplinkdemo.SECOND" />
           <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
</activity>
val intent = Intent("com.lzp.deeplinkdemo.SECOND")
intent.addCategory(Intent.CATEGORY_DEFAULT)
startActivity(intent)

In addition to action and category, there is also an implicit start use to configure data:

<data
     android:scheme="xxxx"
     android:host="xxxx"
     android:port="xxxx"
     android:path="xxxx"
     android:pathPattern="xxxx"
     android:pathPrefix="xxxx"
     android:mimeType="xxxx"/>

scheme: protocol type. We can customize it. Generally, it is the abbreviation of project or company, String

host: domain name address, String

Port: port, int.

Path: access path, String

pathPrefix: prefix of access path, String

pathPattern: the matching format of access path, which is more flexible than path and pathPrefix

mimeType: resource type, such as common: video/*, image/png, text/plain.

Through these configuration items, we find that data actually binds a Uri address for the current page, so that the Activity can be opened directly through the Uri.

Review the structure of Uri:

<scheme> :// <host> : <port> / [ <path> | <pathPrefix> | <pathPattern> ]

Example: https://zhidao.baidu.com/question/2012197558423339788.html

scheme and host cannot be defaulted, otherwise the configuration is invalid; path, pathPrefix and pathPattern are generally specified, and pathPattern and host cannot be used at the same time; mimeType can not be set. If it is set, mimeType must be added during jump, otherwise it cannot be matched to Activity.

Now let's modify the intent filer of SecondActivity:

<activity
    android:name=".SecondActivity"
    android:exported="true">

        <intent-filter>
            <action android:name="com.lzp.deeplinkdemo.SECOND" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>

        <intent-filter>              

            <data
                android:scheme="lzp"
                android:host="demo"
                android:port="8888"
                android:path="/second"
                android:pathPattern="/second"
                android:pathPrefix="/second"
                android:mimeType="text/plain"/>

        </intent-filter>
</activity>

Open SecondActivity:

val intent = Intent()
intent.setDataAndType(Uri.parse("lzp://demo:8888/second"), "text/plain")
startActivity(intent)

Now the page can be opened in App, can it be opened normally with web? First, configure the intent filter of MainActivity:

<activity
    android:name=".MainActivity"
    android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <intent-filter>
            <data
                android:scheme="lzp"
                android:host="demo"
                android:path="/main"/>
        </intent-filter>

</activity>

The Web needs to open url links, so we don't need to configure mimeType,

Write a simple Html page:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta http-equiv="Content-Style-Type" content="text/css">
  <title></title>
  <meta name="Generator" content="Cocoa HTML Writer">
  <meta name="CocoaVersion" content="1561.4">
  <style type="text/css">
    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 17.0px; font: 12.0px 'Songti SC'; color: #000000; -webkit-text-stroke: #000000; min-height: 17.0px}
    span.s1 {font-kerning: none}
  </style>
</head>
<body>
<a href="lzp://Demo / main "> open main</a>
</html>

Html page added a link, click to openlzp://demo/main This address. Import html into mobile phone, open it with browser, click "open app", no response!!!

That's right. If we just configure data and the Web can't open our Activity through the url address, how can we solve this problem?

/**
     * Activities that can be safely invoked from a browser must support this
     * category.  For example, if the user is viewing a web page or an e-mail
     * and clicks on a link in the text, the Intent generated execute that
     * link will require the BROWSABLE category, so that only activities
     * supporting this category will be considered as possible actions.  By
     * supporting this category, you are promising that there is nothing
     * damaging (without user intervention) that can happen by invoking any
     * matching Intent.
     */
    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
    public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";

We also need to configure:

<category android:name="android.intent.category.BROWSABLE" />

It is stated in the official notes that the browser is required to open the Activity and this classification needs to be set. For example, for mail, only activities with this category set will be considered to be opened. After adding this configuration, click again to see if it works.

If you really try it yourself, you'll find it doesn't work. At this time, we need to review the usage of action and category:

First of all, you need to try to match the action. After the action matches successfully, you will continue to match the set category, so there is no effect to match the category alone.

Because we want to open only one page, we set:

/**
     * Activity Action: Display the data to the user.  This is the most common
     * action performed on data -- it is the generic action you can use on
     * a piece of data to get the most reasonable thing to occur.  For example,
     * when used on a contacts entry it will view the entry; when used on a
     * mailto: URI it will bring up a compose window filled with the information
     * supplied by the URI; when used with a tel: URI it will invoke the
     * dialer.
     * <p>Input: {@link #getData} is URI from which to retrieve data.
     * <p>Output: nothing.
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_VIEW = "android.intent.action.VIEW";

The official note indicates that Action view represents the page displaying data. The default Action of the system is Action view. Add Action view and click Open app again.

Still not, but different from the previous one, there is a prompt window to start the app, but the app flashes back. Look at the crash log:

09-06 14:35:15.459 1216-3270/? W/IntentResolver: resolveIntent failed: found match, but none with CATEGORY_DEFAULT
09-06 14:35:15.473 26708-26708/? E/AKAD: thread:Thread[main,5,main]
    android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=lzp://demo/main?id=111 flg=0x10000000 (has extras) }
        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)
        at android.app.Activity.startActivityIfNeeded(Activity.java:4420)
        at android.app.Activity.startActivityIfNeeded(Activity.java:4367)
        at org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl$3.onClick(ExternalNavigationDelegateImpl.java:239)
        at com.qihoo.browser.dialog.CustomDialog$3.onClick(CustomDialog.java:274)
        at android.view.View.performClick(View.java:5267)
        at android.view.View$PerformClick.run(View.java:21249)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5541)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635)

It is clear on the log that the matching page is found, but the category [default] is not set. It seems that the Web uses url to open links. You must set category [default]. After adding it, take a look at our complete xml configuration:

<activity
    android:name=".MainActivity"
            android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data
                android:scheme="lzp"
                android:host="demo"
                android:path="/main"/>
        </intent-filter>
</activity>

Finally, let's see the effect:

 

So how to pass parameters to app through url? To realize this is also very simple. First of all, we know that if we want to add parameters to the url, we can splice key=value directly after the url, for example:

http://www.baidu.com/s?wd=android

Where wd=android is the parameter we want to add. Now suppose we need to pass a parameter id for Activity, we can modify the uri to:

lzp://demo/main?id=111

The method for the client to receive the parameter with key as id:

if (intent != null && intent.data != null) {
       Log.e("lzp", intent.data.getQueryParameter("id"))
}

If we only receive parameters, the client does not need to make any changes, but there is a situation here. If we have to pass the id, what can we do if we don't pass the id and skip? We have two ways to solve this problem:

1. Add else judgment in the if statement just now. When the parameter is empty, finish the operation.

2. Through pathPattern, parameters must be set through wildcard.

Let's look at the second implementation:

<data
    android:pathPattern="lzp://demo/main?id=*"
    android:scheme="lzp" />

As mentioned before, pathPattern cannot be used with host at the same time, so we can only delete host. pathPattern matches the entire Uri, so we can specify multiple parameters. But Android manifest.xml will report an error, so we can ignore it

summary

In fact, the implementation principle of DeepLink is so simple, but we don't understand implicit startup enough. Do you want to add DeepLink to your App? Let's try it~

 

 

 

 

 

Published 29 original articles, won praise 7, visited 7139
Private letter follow

Topics: Android Java Mobile xml