本文 github 仓库链接
本文测试主要版本信息
package
version
react
18.2.0
react-native
0.71.2
android
13
手机
小米10
1. Deep Link
的日常使用场景
从并夕夕app分享到微信,邀请好友砍一刀,其中有的链接打开后是h5页面,h5页面打开后,弹出提示,“立即打开xx应用”,或者打开后,要求右上角在本机浏览器中打开,然后浏览器里弹出提示,“当前网站请求打开xx应用”。
从app中,或者短信中打开微信小程序,链接如weixin://dl/business/?t= *TICKET*
。
2. 什么是Deep Link
? 文档 中提到:
Mobile apps have a unique vulnerability that is non-existent in the web: deep linking. Deep linking is a way of sending data directly to a native application from an outside source. A deep link looks like app:// where app is your app scheme and anything following the // could be used internally to handle the request.
Deep links are not secure and you should never send any sensitive information in them.
总结:
Deep Link
是手机端app中独有的;
Deep Link
是一种通过外面链接把数据发送到app里的方法;
Deep Link
长得像app://
,其中“app ”是你自定义的scheme,“*//*”后面跟的内容,用来给app处理的;
Deep Link
是不安全的,不要通过这种方式发送敏感数据。
3. 开启Deep Link
下面xml中有注释掉的配置,是因为准备配置app link,验证app link,通过后才允许拉起app,配置好后,但验证失败了,下面会讲
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <manifest > <queries > <intent > <action android:name ="android.intent.action.VIEW" /> <data android:scheme ="yourScheme" /> </intent > </queries > </manifest >
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <manifest > <activity android:name =".MainActivity" android:launchMode ="singleTask" > <intent-filter > <action android:name ="android.intent.action.VIEW" /> <data android:scheme ="yourScheme" /> </intent-filter > </activity > </manifest >
4. 处理Deep Link 4.1 监听其它app通过deep Link拉起本app 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React , {Fragment , useEffect, memo} from 'react' ;import {Linking } from 'react-native' ;import {useNavigate} from 'react-router-native' ;const DeepLinksListener = ( ) => { const navigate = useNavigate (); useEffect (() => { const cb = data => { console .log ('Linking data==>' , data); const url = data.url ; const schemas = [ 'yourScheme://' , 'https://my.applink.com/' , ]; if (schemas.some (s => url.startsWith (s))) { let newUrl = url .replace (schemas[0 ], '' ) .replace (schemas[2 ], '' ); navigate && navigate (`/${newUrl} ` ); } }; Linking .addEventListener ('url' , cb); }, [navigate]); return <Fragment /> ; };export default memo (DeepLinksListener );
4.2 通过Deep link打开其它app 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import {Linking } from 'react-native' ;const handleOpenUrl = async linkUrl => { try { const supported = await Linking .canOpenURL (linkUrl); console .log ('supported' , supported); if (supported) { await Linking .openURL (linkUrl); } else { Alert .alert ('提示' , `无法打开${linkUrl} ` , [{text : '确定' }]); } } catch (error) { console .error (error); Alert .alert ('提示' , `无法打开${linkUrl} ,${error.message} ` , [ {text : '确定' }, ]); } };
测试打开微信App
handleOpenUrl ("weixin://" );
测试打开另一个RN App
前提是另一个app中AndroidManifest.xml中已经配置好deep link
handleOpenUrl ("mydeeplink://" );
await Linking.canOpenURL("mydeeplink://")
直接返回false
直接通过await Linking.openURL("mydeeplink://")
可以拉起另一个RN App
感觉 Linking.canOpenURL
有问题,知道原因的告诉我一声,测试机型是小米13,android13版本
测试scheme为https和http的链接
handleOpenUrl ("https://cn.bing.com" );
测试打开app设置页
await Linking .openSettings ();
通过adb指令测试
adb shell am start -W -a android.intent.action.VIEW -d "yourScheme://detail/xxx" com.your-app-name
4.3 被其他App拉起后获取Deep Link const initialUrl = await Linking .getInitialURL ();console .log ('initialUrl:' , initialUrl);
在开发debugger模式下,Linking.getInitialURL始终返回null
如果app是被其它app拉起的,会获取到deep link值,反之始终为null
5. App Link 5.1 App Link 与 Deep Link比较
文档
Deep Link
App Link
intent 网址架构
http、https 或自定义架构
需要 http 或 https
intent 操作
intent 操作 任何操作
需要 android.intent.action.VIEW
intent 类别
任何类别
需要 android.intent.category.BROWSABLE 和 android.intent.category.DEFAULT
链接验证
无
需要通过 HTTPS 协议在您的网站上发布 Digital Asset Links 文件
用户体验
可能会显示一个消除歧义对话框,以供用户选择用于打开链接的应用
无对话框;您的应用会打开以处理您的网站链接
兼容性
所有 Android 版本
Android 6.0 及更高版本
5.2 App Link配置步骤 5.2.1 AndroidManifest.xml配置 <manifest > <meta-data android:name ="asset_statements" android:resource ="@string/asset_statements" /> <activity ... > <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 ="https" android:host ="my.applink.com" android:pathPrefix ="/detail" /> </intent-filter > </activity > </manifest >
<resources > <string name ="asset_statements" > [{ \"relation\": [\"delegate_permission/common.share_location\"], \"target\": { \"namespace\": \"web\", \"site\": \"https://my.applink.com\" } }] </string > </resources >
5.2.2 本地部署一个https服务
可以使用nginx或者live-server 等,我这里使用了live-server
使用openssl创建https证书
openssl genrsa -out server.key 1024
openssl req -new -key server.key -out server.csr
第一步的私钥和第二步的请求文件生成证书(下面失效时间为365天)
openssl x509 -req -in server.csr -out server.crt -signkey server.key -days 3650
live-server开启https
如果有现成的https部署的网站,就不用本地这么麻烦了,直接部署在线上
把上一步生成的server.crt
和server.key
都复制到live-server项目根目录里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 var fs = require ("fs" );var path = require ("path" );var liveServer = require ("live-server" );var params = { port : 443 , host : "0.0.0.0" , root : "./" , open : false , ignore : "scss,my/templates" , file : "index.html" , wait : 1000 , mount : [["/components" , "./node_modules" ]], logLevel : 2 , middleware : [ function (req, res, next ) { if (req.url === "/.well-known/assetlinks.json" ) { const data = fs.readFileSync ( path.join (__dirname, "./.well-known/assetlinks.json" ) ); res.writeHead (200 , { "Content-Disposition" : "attachment; filename=assetlinks.json" , "Content-Type" : "application/json" , }); res.end (data); } next (); }, ], https : { cert : fs.readFileSync (path.join (__dirname, "./server.crt" )), key : fs.readFileSync (path.join (__dirname, "./server.key" )), passphrase : "12345" , }, }; liveServer.start (params);
获取App debug.keystore的SHA256签名
这是在react native项目的根目录执行,debug.keystore的密钥库口令是 android
keytool -list -v -keystore android/app/debug.keystore
live-server项目根目录创建.well-known/assetlinks.json
文件
这里可以使用线上的语句列表生成器和测试器
把上面生成的SHA256签名填入下面的sha256_cert_fingerprints
中
[ { "relation" : [ "delegate_permission/common.handle_all_urls" ] , "target" : { "namespace" : "android_app" , "package_name" : "com.example" , "sha256_cert_fingerprints" : [ "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5" ] } } ]
修改本地hosts
192.168.1.5 my.applink.com
验证访问assetlinks.json
文件
浏览器输入https://my.applink.com/.well-known/assetlinks.json
可以正常打开下载,说明部署正常
手机端验证访问assetlinks.json
文件
手机要和电脑处在同一局域网下(wifi下),修改手机的wifi代理配置,填入主机名(电脑ip)和端口号,这样手机就可以访问下载assetlinks.json
文件了,代理我这里用的是anyproxy 。
5.2.3 测试App Link
测试网址 intent
adb shell am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d "http://my.applink.com/detail/xxx" \ com.your-app-name
测试结果 :经过实际测试可以正常拉起app,并跳转到app指定页面
在另一个RN项目中拉起测试
await Linking .openURL ("https://my.applink.com/detail/xxx" )
测试结果 : 经过实际测试,只拉起了手机本地浏览器,没有拉起app。有可能是上面生成的https证书,手机不认可(浏览器弹出了警告,点击继续才打开网页)
调用android原生方法传入app link拉起测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.your-app-name;import android.content.Intent;import android.net.Uri;import com.facebook.react.bridge.NativeModule;import com.facebook.react.bridge.ReactApplicationContext;import com.facebook.react.bridge.ReactContext;import com.facebook.react.bridge.ReactContextBaseJavaModule;import com.facebook.react.bridge.ReactMethod;import com.facebook.react.bridge.Promise;public class OpenAppLinkModule extends ReactContextBaseJavaModule { private static ReactApplicationContext reactContext; private static final String OPENLINK_OK = "OPENLINK_OK" ; private static final String OPENLINK_ERR = "OPENLINK_ERR" ; @Override public String getName () { return "OpenAppLink" ; } public OpenAppLinkModule (ReactApplicationContext context) { super (context); reactContext = context; } @ReactMethod public void handleOpenAppLink (String appLinkUrl, String appPackageName, Promise promise) { try { Intent intent = new Intent (Intent.ACTION_VIEW, Uri.parse(appLinkUrl)); intent.setPackage(appPackageName); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); reactContext.startActivity(intent); promise.resolve(OPENLINK_OK); } catch (Exception e) { promise.reject(OPENLINK_ERR, e); } } }
js端调用android原生方法
import {NativeModules } from 'react-native' ;try { const res = await NativeModules .OpenAppLink .handleOpenAppLink ( 'https://my.applink.com/detail/xxx' , 'com.your-app-name' , ); console .log ('openAppLink res:' , res); } catch (error) { console .error (error); }
测试结果 : 经过实际测试,可以正常拉起app,并跳转到指定页面
顺便测试下h5页面拉起app
在live-server的根目录index.html
中,添加a链接拉起app
<a id ="deepLinkId" href ="yourAppScheme://detail/xxx" target ="_blank" > 打开 app 测试</a >
如果想在打开h5页面时,免去用户的点击操作,可以添加js,主动派发点击事件
window .addEventListener ("DOMContentLoaded" , () => { var btn = document .querySelector ("#deepLinkId" ); var event = document .createEvent ("MouseEvent" ); event.initMouseEvent ("click" , true , true , document .defaultView , 0 , 0 , 0 , 0 , 0 , false , false , false , false , 0 , null ); btn.dispatchEvent (event); });
测试结果 :经过实际测试,点击a链接,浏览器弹出提示,当前网站请求打开xx app
,点击允许
,成功拉起app,并跳转到指定页
5.3 存在的问题和后续挣扎
通过Linking.openURL
这个api,传入app link只能拉起本地浏览器,没拉起app
Linking.canOpenURL
这个api,传入deep link链接,返回false,但通过Linking.openURL
又能打开,有问题
上面费那么大劲,本地部署一个https,把本地服务暂停掉,通过android原生app link拉起目标app,发现其实也是能拉起的,这等于是压根就没有进行校验,好家伙,浪费我感情
经过搜索,chatGPT提示,有说是因为我本地https证书的问题,两边RN App项目都要处理
android/app/src/main/res/raw
把本地生成的https证书server.crt
复制到这里
android/app/src/main/res/xml/network_security_config.xml
<network-security-config > <base-config > <trust-anchors > <certificates src ="@raw/server" /> </trust-anchors > </base-config > </network-security-config >
android/app/src/main/java/com/mydeeplink/TrustServerCrtModule.java
创建原生方法,让app读取并信任证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.your-app-name;import com.facebook.react.bridge.NativeModule;import com.facebook.react.bridge.ReactApplicationContext;import com.facebook.react.bridge.ReactContext;import com.facebook.react.bridge.ReactContextBaseJavaModule;import com.facebook.react.bridge.ReactMethod;import com.facebook.react.bridge.Promise;import java.io.InputStream;import java.security.KeyStore;import java.security.SecureRandom;import java.security.cert.CertificateFactory;import java.security.cert.X509Certificate;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory;public class TrustServerCrtModule extends ReactContextBaseJavaModule { private static ReactApplicationContext reactContext; private static final String SERVER_READ_OK = "server.crt已设置为信任" ; private static final String SERVER_READ_ERROR = "SERVER_READ_ERROR" ; @Override public String getName () { return "TrustServerCrt" ; } public TrustServerCrtModule (ReactApplicationContext context) { super (context); reactContext = context; } @ReactMethod public void readServerCrt (Promise promise) { try { InputStream certStream = reactContext.getResources().openRawResource(R.raw.server); CertificateFactory cf = CertificateFactory.getInstance("X.509" ); X509Certificate cert = (X509Certificate) cf.generateCertificate(certStream); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null , null ); keyStore.setCertificateEntry("ca" , cert); SSLContext sslContext = SSLContext.getInstance("TLS" ); TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); sslContext.init(null , trustManagerFactory.getTrustManagers(), new SecureRandom ()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); promise.resolve(SERVER_READ_OK); } catch (Exception e) { promise.reject(SERVER_READ_ERROR, e); } } }
await NativeModules .TrustServerCrt .readServerCrt ();
结果 :一顿猛虎操作,毛用都没,再次浪费我感情
assetlinks.json
部署在公网上,并且能够通过https正常访问/.well-known/assetlinks.json
,然后再使用Linking.openURL
调用
结果 :只是拉起本地浏览器,未拉起app
准备用android studio来配置,当我把项目导入后,点击Tools -- App Links Assistant
弹出面板后,在第一步Open URL Mapping Editor
中填了Host、Path和pathPrefix
后,死活选择不了Activity
,无语。。。
一番搜索,说是国内网络原因,app link使用有问题,知道的说一声;
6.参考资料
React Native Linking
Android 应用链接
添加 Android App Links