一、DeepLink的应用场景

DeepLink简单理解就是通过在手机上点击一个链接后能实现如下功能:

  1. 如果目标App没有启动,那么就拉起App,并跳转到App内指定页面
  2. 如果目标App已经启动,那么就把App拉到前台并跳转到App内指定页面

二、DeepLink的实现思路

在Android开发中,可以通过在清单文件中配置scheme来实现页面跳转,所以可以通过scheme匹配的方式来实现DeepLink的功能。配置方式大概分为三种:

  1. 为每一个要跳转的Activity都指定一个对应的匹配条件,如果页面太多的话,这种方式管理起来不太方便,而且没有办法对App全局配置信息、用户状态等进行统一处理
  2. 配置闪屏页面为匹配页面,闪屏页一般都是App冷启动时才会出现,而且打开首页后,闪屏页就会关闭,这种方式在App没有启动的情况下可以很好的处理对应的Intent信息,但是如果App已经启动过了,在去拉起闪屏页就不合理了
  3. 配置首页为匹配页面,首页在App中一般都是常驻的,一般情况下首页关闭就意味着App的退出,所以可以在首页中统一处理匹配scheme得到的Intent信息,然后进行统一的跳转分发(需要将首页Activity的启动模式设置为singleTask以防止首页创建多个页面)

三、DeepLink的实现案例

下面使用上述的方式3写个Demo进行验证,实现步骤如下:

  1. 提前定义好自己的scheme、host等信息配置到清单文件里面,scheme是必须要有的,像host等信息可以配置也可以没有,我这里配置了scheme和host两个条件,其中sheme是“link”,host是“cn.znh.deeplinkdemo”,清单文件配置如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.znh.deeplinkdemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

	<!--闪屏页设置为启动页-->
        <activity android:name=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!--配置首页-->
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTask">
            <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:host="cn.znh.deeplinkdemo"
                    android:scheme="link" />
            </intent-filter>
        </activity>

    	<!--普通的Activity-->
        <activity android:name=".Link1Activity" />
        <activity android:name=".Link2Activity" />
        <activity android:name=".Link3Activity" />
    </application>

</manifest>
  1. 在首页Activity的onCreate方法和onNewIntent方法里面,接收Intent参数进行相应的跳转处理,首页代码如下:
package cn.znh.deeplinkdemo;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
* Created by znh on 2018/12/23.
* <p>
* 首页
*/
public class MainActivity extends AppCompatActivity {

   //定义的scheme
   private static final String SCHEME_VALUE = "link";

   //定义的host
   private static final String HOST_VAULE = "cn.znh.deeplinkdemo";

   /**
    * 首次启动MainActivity调用该方法
    *
    * @param savedInstanceState
    */
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       schemeIntent(getIntent());
   }

   /**
    * 启动已经存在的MainActivity调用该方法(singleTask启动模式)
    *
    * @param intent
    */
   @Override
   protected void onNewIntent(Intent intent) {
       super.onNewIntent(intent);
       schemeIntent(intent);
   }

   /**
    * 处理intent事件
    *
    * @param intent
    */
   private void schemeIntent(Intent intent) {
       if (intent == null || intent.getData() == null) {
           return;
       }

       //获取Uri
       Uri uri = intent.getData();

       //打印出uri里取出的Scheme和Host
       Log.e("schemeIntent", "getScheme:" + uri.getScheme());
       Log.e("schemeIntent", "getHost:" + uri.getHost());

       //判断取出的Scheme和Host是否和自己配置的一样,如果一样进行相应的处理,否则不处理
       if (!SCHEME_VALUE.equals(uri.getScheme()) || !HOST_VAULE.equals(uri.getHost())) {
           return;
       }

       //如果Scheme和Host匹配成功,取出uri中的参数并进行相应的业务处理
       String type = uri.getQueryParameter("type");
       String id = uri.getQueryParameter("id");

       //打印uri里取出的参数
       Log.e("schemeIntent", "type:" + type);
       Log.e("schemeIntent", "id:" + id);

       //进行统一的跳转分发
       if ("1".equals(type)) {
           Intent intent1 = new Intent(this, Link1Activity.class);
           startActivity(intent1);
       } else if ("2".equals(type)) {
           Intent intent2 = new Intent(this, Link2Activity.class);
           startActivity(intent2);
       } else if ("3".equals(type)) {
           Intent intent3 = new Intent(this, Link3Activity.class);
           startActivity(intent3);
       }
   }
}

上述两个步骤就可以实现deeplink的效果了,可以在AndroidStudio里输入如下命令进行测试:

adb shell am start -a android.intent.action.VIEW -d "link://cn.znh.deeplinkdemo?'type=2&id=335'"

观察结果发现页面跳转到Link2Activity了,log打印结果如下:

E/schemeIntent: getScheme:link
E/schemeIntent: getHost:cn.znh.deeplinkdemo
E/schemeIntent: type:2
E/schemeIntent: id:335

四、闪屏页的问题处理

经过上面两个步骤,确实已经可以实现deeplink的功能了,但是还有个问题,那就是如果App还没有启动的情况下,由于直接拉起的是首页页面,并没有经过闪屏页(如果App已经启动过了,不需要走闪屏页,直接走首页然后进行对应页面跳转是没有问题的),那么怎么解决这个问题呢,这里想到的一个解决方案是记录一个是否是经闪屏页启动的一个标志位,如果是就正常处理,如果不是就重新开启闪屏页。具体实现如下:

  1. 在闪屏页面跳转到首页时,在Intent中传递过去一个标志位参数isSplashLanuch,以标识闪屏页已经启动过了
//跳转到首页
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putExtra("isSplashLanuch", true);
startActivity(intent);
finish();
  1. 在首页页面中获取isSplashLanuch的值来判断闪屏页面是否已经启动过(需要注意,在onNewIntent方法中要将isSplashLanuch的值永远设置为true),如果为true不进行特殊处理,如果为false就关闭首页并开启闪屏页,还要传递给闪屏页uri数据,方便跳转场景还原,首页处理Intent的方法如下:
/**
 * 处理intent事件
 *
 * @param intent
 */
private void schemeIntent(Intent intent) {
    if (intent == null || intent.getData() == null) {
        return;
    }

    //获取Uri
    Uri uri = intent.getData();

    //打印出uri里取出的Scheme和Host
    Log.e("schemeIntent", "getScheme:" + uri.getScheme());
    Log.e("schemeIntent", "getHost:" + uri.getHost());

    //判断取出的Scheme和Host是否和自己配置的一样,如果一样进行相应的处理,否则不处理
    if (!SCHEME_VALUE.equals(uri.getScheme()) || !HOST_VAULE.equals(uri.getHost())) {
        return;
    }

    //如果闪屏页启动过了,就不处理,否则关闭首页打开闪屏页
    if (!isSplashLanuch) {
        Intent intentSplash = new Intent(this, SplashActivity.class);
        intentSplash.setData(uri);//设置uri数据,方便场景还原
        startActivity(intentSplash);
        finish();
        return;
    }

    //如果Scheme和Host匹配成功,取出uri中的参数并进行相应的业务处理
    String type = uri.getQueryParameter("type");
    String id = uri.getQueryParameter("id");

    //打印uri里取出的参数
    Log.e("schemeIntent", "type:" + type);
    Log.e("schemeIntent", "id:" + id);

    //进行统一的跳转分发
    if ("1".equals(type)) {
        Intent intent1 = new Intent(this, Link1Activity.class);
        startActivity(intent1);
    } else if ("2".equals(type)) {
        Intent intent2 = new Intent(this, Link2Activity.class);
        startActivity(intent2);
    } else if ("3".equals(type)) {
        Intent intent3 = new Intent(this, Link3Activity.class);
        startActivity(intent3);
    }
}
  1. 在闪屏页执行结束跳转到首页时,将uri数据带到首页进行场景还原,跳转到首页的代码如下:
//跳转到首页,并重新设置拿到的uri数据
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
if (getIntent() != null && getIntent().getData() != null) {
    intent.setData(getIntent().getData());
}
intent.putExtra("isSplashLanuch", true);
startActivity(intent);
finish();

在次输入命令进行测试,观察结果,冷启动时可以走闪屏页面了,跳转逻辑页正常。

五、闪屏页和首页的完整代码

  1. 闪屏页完整代码:
package cn.znh.deeplinkdemo;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

/**
 * Created by znh on 2018/12/23.
 * <p>
 * 闪屏页
 */
public class SplashActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);

        //延迟3秒跳转到首页
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //跳转到首页,并重新设置拿到的uri数据
                Intent intent = new Intent(SplashActivity.this, MainActivity.class);
                if (getIntent() != null && getIntent().getData() != null) {
                    intent.setData(getIntent().getData());
                }
                intent.putExtra("isSplashLanuch", true);
                startActivity(intent);
                finish();
            }
        }, 3000);
    }
}
  1. 首页的完整代码:
package cn.znh.deeplinkdemo;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
 * Created by znh on 2018/12/23.
 * <p>
 * 首页
 */
public class MainActivity extends AppCompatActivity {

    //定义的scheme
    private static final String SCHEME_VALUE = "link";

    //定义的host
    private static final String HOST_VAULE = "cn.znh.deeplinkdemo";

    //闪屏页是否启动过了
    private boolean isSplashLanuch = false;

    /**
     * 首次启动MainActivity调用该方法
     *
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        isSplashLanuch = getIntent().getBooleanExtra("isSplashLanuch", false);
        schemeIntent(getIntent());
    }

    /**
     * 启动已经存在的MainActivity调用该方法(singleTask启动模式)
     *
     * @param intent
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        isSplashLanuch = true;
        schemeIntent(intent);
    }

    /**
     * 处理intent事件
     *
     * @param intent
     */
    private void schemeIntent(Intent intent) {
        if (intent == null || intent.getData() == null) {
            return;
        }

        //获取Uri
        Uri uri = intent.getData();

        //打印出uri里取出的Scheme和Host
        Log.e("schemeIntent", "getScheme:" + uri.getScheme());
        Log.e("schemeIntent", "getHost:" + uri.getHost());

        //判断取出的Scheme和Host是否和自己配置的一样,如果一样进行相应的处理,否则不处理
        if (!SCHEME_VALUE.equals(uri.getScheme()) || !HOST_VAULE.equals(uri.getHost())) {
            return;
        }

        //如果闪屏页启动过了,就不处理,否则关闭首页打开闪屏页
        if (!isSplashLanuch) {
            Intent intentSplash = new Intent(this, SplashActivity.class);
            intentSplash.setData(uri);//设置uri数据,方便场景还原
            startActivity(intentSplash);
            finish();
            return;
        }

        //如果Scheme和Host匹配成功,取出uri中的参数并进行相应的业务处理
        String type = uri.getQueryParameter("type");
        String id = uri.getQueryParameter("id");

        //打印uri里取出的参数
        Log.e("schemeIntent", "type:" + type);
        Log.e("schemeIntent", "id:" + id);

        //进行统一的跳转分发
        if ("1".equals(type)) {
            Intent intent1 = new Intent(this, Link1Activity.class);
            startActivity(intent1);
        } else if ("2".equals(type)) {
            Intent intent2 = new Intent(this, Link2Activity.class);
            startActivity(intent2);
        } else if ("3".equals(type)) {
            Intent intent3 = new Intent(this, Link3Activity.class);
            startActivity(intent3);
        }
    }
}