近日使用React Native Linking踩過(guò)的坑

使用react native(以下簡(jiǎn)稱rn)開(kāi)發(fā)移動(dòng)端app已經(jīng)有四個(gè)月的時(shí)間了(包括第一個(gè)月的上手),感謝rn,讓前端開(kāi)發(fā)人員也能夠開(kāi)發(fā)原生的app蒿辙。前幾天遇到一個(gè)需求:打開(kāi)第三方的支付應(yīng)用并監(jiān)聽(tīng)返回的結(jié)果自阱。聽(tīng)上去這個(gè)需求并不難,然而使用rn來(lái)實(shí)現(xiàn)就會(huì)遇到大大小小的坑关拒。為了能讓其他開(kāi)發(fā)人員少走彎路,在這里總結(jié)一下庸娱。

使用Linking

寫這篇博客的原因還有一個(gè):網(wǎng)上有很多關(guān)于Linking的博客着绊,然而有深度的文章少之又少,大部分都是簡(jiǎn)單介紹了Linking的使用方法(我搜過(guò)好幾篇文章內(nèi)容和代碼都是一樣的)熟尉。

Linking基本使用方法

這里我建議去rn的中文官網(wǎng)學(xué)習(xí)归露,那里講解的十分詳細(xì)。通過(guò)查看文檔我們了解到斤儿,Linking使用url來(lái)喚起系統(tǒng)應(yīng)用或鏈接剧包。其實(shí)Linking還可以喚起其他的app,前提條件是你的手機(jī)上已經(jīng)安裝了它往果。

喚起其他app

使用Linking喚起其他app比較簡(jiǎn)單疆液,只需要簡(jiǎn)單的兩個(gè)步驟:1.檢查該app能否被喚起,也就是檢查該app是否已安裝成功陕贮;2.喚起并傳遞參數(shù)堕油。

Linking提供了canOpenURL這個(gè)方法,用來(lái)檢測(cè)某個(gè)url是否可以打開(kāi):

Linking.canOpenURL('appName://').then(canOpen=>{
    ...
})

使用Linking打開(kāi)app也比較簡(jiǎn)單飘蚯,調(diào)用openURL方法即可:

Linking.openURL('appName://?params');

為了方便演示馍迄,我準(zhǔn)備了兩個(gè)app:lka和lkb。這兩個(gè)應(yīng)用功能比較簡(jiǎn)單局骤,只含有一個(gè)button攀圈,點(diǎn)擊的時(shí)候喚起另外一個(gè)app,同時(shí)傳遞參數(shù)峦甩。被喚起的app獲取參數(shù)并alert出來(lái)赘来。


image.png

image.png

現(xiàn)在现喳,我需要在lka里喚起lkb,代碼是這樣的:

Linking.canOpenURL('lkb://').then(canOpen=>{
    if(canOpen){
        Linking.openURL('lkb://?orderId=1');
    }
});

你如果直接點(diǎn)擊button的話是肯定不會(huì)跳轉(zhuǎn)的犬辰,因?yàn)閏anOpen是false嗦篱。可能有些人會(huì)問(wèn):我明明已經(jīng)安裝了lkb幌缝,為什么會(huì)打不開(kāi)灸促?這里就要說(shuō)到scheme了,我們可以把它理解為一個(gè)app的標(biāo)識(shí)涵卵,當(dāng)url的協(xié)議部分與scheme匹配時(shí)浴栽,app就會(huì)被打開(kāi)。

我們需要在AndroidManifest.xml里進(jìn)行相關(guān)的配置:

<activity
    android:name=".MainActivity"
/*add  -->*/ android:launchMode="singleTask"
    android:label="@string/app_name"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
    android:windowSoftInputMode="stateAlwaysHidden|adjustPan"
>
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>    
    </intent-filter>
/*add start*/
    <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="lka" />
    </intent-filter>
/*add end*/
</activity>

我們添加了兩塊代碼:launchMode和intent-filter轿偎。關(guān)于launchMode可以參考這篇文章學(xué)習(xí)典鸡。我們新添加了一個(gè)intent-filter,關(guān)于intent-filter的相關(guān)知識(shí)可以自行上網(wǎng)搜索坏晦。Intent-filter顧名思義就是意圖過(guò)濾器萝玷,它就像過(guò)濾器一樣篩選每次傳過(guò)來(lái)的url,只要有符合條件的url就會(huì)執(zhí)行intent-filter里面的相關(guān)操作昆婿。

在本代碼中球碉,我們?cè)趇ntent-filter里配置了scheme,只要url的協(xié)議為lka就會(huì)打開(kāi)lka app仓蛆。請(qǐng)注意汁尺,不要把兩個(gè)intent-filter合并到一起,雖然你的app能夠正常運(yùn)行多律,但是你將會(huì)在手機(jī)上找不到app的圖標(biāo)。

再次點(diǎn)擊openLkb按鈕搂蜓,喚起成功狼荞。


image.png
//lkb
componentDidMount(){
    Linking.getInitialURL().then(url=>{
        alert(url);
    })
}

開(kāi)始踩坑

現(xiàn)在,lka已經(jīng)能夠成功喚起lkb了帮碰,并且傳遞的參數(shù)在lkb里也能接收到相味,那么反過(guò)來(lái)也是一樣的?現(xiàn)在我們?cè)黾右幌滦枨笱惩欤灰猯ka從后臺(tái)運(yùn)行到了前臺(tái)或者首次打開(kāi)均彈出url丰涉。

實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,我們需要監(jiān)聽(tīng)app的運(yùn)行狀態(tài)斯碌,需要用到AppState:

//lka
import {
    Linking,
    AppState
} from 'react-native'
...
componentDidMount(){
    AppState.addEventListener('change',(appState)=>{
        if(appState=='active'){
            Linking.getInitialURL().then(url=>{
                alert('stateChange'+url)        
            })
        }
    })
    Linking.getInitialURL().then(url=>{
        alert('didmount:'+url);
    })
}
//lkb
openLka(){
    Linking.canOpenURL('lka://').then(res=>{
        if(res){
            Linking.openURL('lka://?name=sunnychuan&age=23');
        }
    });
}

同樣的一死,為lka配置好AndroidManifest.xml,把scheme配置成lka傻唾。我們首先把lka關(guān)掉投慈,然后在lkb里喚起它承耿,結(jié)果如下:


image.png

我們通過(guò)任務(wù)管理切回到lkb,然后點(diǎn)擊按鈕再次喚起lka伪煤,你得到的結(jié)果還是正確的:


image.png

先別急著高興加袋,我們把lka和lkb都關(guān)掉,重新打開(kāi)lka抱既,你將得到“didmount:null”的結(jié)果职烧。這是當(dāng)然的,因?yàn)槟闶亲约捍蜷_(kāi)的嘛防泵。

然后蚀之,我們通過(guò)lka喚起lkb,再通過(guò)lkb喚起lka择克,你得到的結(jié)果如下:


image.png

發(fā)現(xiàn)問(wèn)題沒(méi)有恬总?你可以多嘗試幾次,最終會(huì)發(fā)現(xiàn)一個(gè)規(guī)律:AppState.addEventListener里面獲取的url的值永遠(yuǎn)與componentDidMount里直接獲取的url的值相同肚邢。只要首次獲取的是null壹堰,那么以后永遠(yuǎn)都是null;只要首次獲取的是有值的骡湖,那么以后永遠(yuǎn)都是有值的贱纠。

我們看一下Linking的源碼吧:

//node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java
...
@ReactMethod
public void getInitialURL(Promise promise) {
    try {
        Activity currentActivity = getCurrentActivity();
        String initialURL = null;
        if (currentActivity != null) {
            Intent intent = currentActivity.getIntent();
            String action = intent.getAction();
            Uri uri = intent.getData();
            if (Intent.ACTION_VIEW.equals(action) && uri != null) {
                initialURL = uri.toString();
            }
        }
        promise.resolve(initialURL);
    } catch (Exception e) {
        promise.reject(new JSApplicationIllegalArgumentException(
        "Could not get the initial URL : " + e.getMessage()));
      }
}

每一次調(diào)用getInitialURL,android端都會(huì)獲取當(dāng)前的activity响蕴,并且返回activity對(duì)象里面的data值(uri)谆焊。

我們可以把AppState.addEventListener里面獲取的url稱為臟數(shù)據(jù)。通過(guò)上網(wǎng)翻閱相關(guān)資料后我發(fā)現(xiàn)浦夷,原生的android跳轉(zhuǎn)其實(shí)是activity之間的跳轉(zhuǎn)∠绞裕現(xiàn)在回過(guò)頭來(lái)看一下我們的xml,只有一個(gè)activity劈狐。你可以嘗試一下把a(bǔ)ctivity拆成兩個(gè)罐孝,其中一個(gè)專門用來(lái)配置scheme,運(yùn)行結(jié)果并不符合我們的預(yù)期肥缔。

原因是什么呢莲兢?這是因?yàn)閞eact native只配置了一個(gè)activity,整個(gè)應(yīng)用都是在這個(gè)activity里運(yùn)行的续膳。當(dāng)lka尚未啟動(dòng)改艇,由lkb喚起時(shí),lka的activity會(huì)執(zhí)行onCreate生命周期鉤子坟岔,初始化intent谒兄,此時(shí)你將會(huì)得到全新的url:null。當(dāng)lka已經(jīng)運(yùn)行在后臺(tái)炮车,由lkb喚起時(shí)舵变,lka的activity不會(huì)執(zhí)行onCreate方法酣溃,你得到的url還是舊值:null。

解決方案參考了這篇文章纪隙,在android/app/src/main/java/com/lka/MainActivity.java的最下面添加:

@Override
public void onNewIntent(Intent intent){
    super.onNewIntent(intent);
    setIntent(intent);
}

重新打包之后(每次修改android文件夾里面的東西后都需要重新打包才能生效)赊豌,我們?cè)賴L試一下:1.關(guān)掉lka和lkb;2.打開(kāi)lka绵咱,你會(huì)收到null值碘饼;3.喚起lkb;4.由lkb喚起lka悲伶。你得到的結(jié)果如下:


image.png

結(jié)果與我們的預(yù)期相符艾恼。

另一個(gè)問(wèn)題

其實(shí)這里還有一個(gè)潛在的問(wèn)題。同樣的麸锉,通過(guò)lkb喚起lka钠绍,你將接收到正確的參數(shù)“l(fā)ka://?name=sunnychuan&age=23”。然后花沉,我們手動(dòng)將lka運(yùn)行在后臺(tái)柳爽,然后重新讓它運(yùn)行在前臺(tái)(不通過(guò)lkb喚起),你得到的值依舊是“l(fā)ka://?name=sunnychuan&age=23”碱屁。


image.png

image.png

image.png

從代碼上來(lái)看磷脯,這個(gè)結(jié)果是正確的,因?yàn)闆](méi)有人更改activity的url娩脾,所以值一直沒(méi)有改變赵誓;從需求上來(lái)看,這個(gè)結(jié)果是不正確的柿赊。我們假設(shè)lka在監(jiān)聽(tīng)函數(shù)里獲取url的參數(shù)俩功,如果url有參數(shù)就跳轉(zhuǎn)到支付成功頁(yè)面。現(xiàn)在碰声,只要lka由后臺(tái)運(yùn)行到前臺(tái)都會(huì)跳轉(zhuǎn)到支付成功頁(yè)面(沒(méi)準(zhǔn)真的有用戶喜歡來(lái)回切換應(yīng)用)绑雄。這樣顯然是不合理的,我們期望的是:只有l(wèi)ka是由lkb喚起的(無(wú)論lka已經(jīng)運(yùn)行在后臺(tái)還是尚未啟動(dòng))奥邮,才會(huì)跳轉(zhuǎn)到支付成功頁(yè)面。

我的思路是罗珍,在getInitialURL.then里洽腺,首先將activity的intent重置成默認(rèn)值,這需要我們自己封裝android方法覆旱,我們先看一下封裝后的代碼:

//lka
import {
    Linking,
    AppState,
    NativeModules
} from 'react-native'
...
componentDidMount(){
    AppState.addEventListener('change',(appState)=>{
        if(appState=='active'){
            Linking.getInitialURL().then(url=>{
                NativeModules.LinkingCustom.resetURL().then(()=>{
                    alert('stateChange'+url)
                });     
            })
        }
    })
    Linking.getInitialURL().then(url=>{
        NativeModules.LinkingCustom.resetURL().then(()=>{
            alert('didmount'+url)
        }); 
    })
}

下面我們來(lái)為lka封裝一下這個(gè)方法蘸朋,如果你是安卓工程師,這點(diǎn)操作就是小兒科扣唱;如果你是前端工程師藕坯,并且對(duì)安卓不了解团南,跟著我一步一步寫,很簡(jiǎn)單炼彪。

CustomLinking

首先吐根,我們需要在與MainActivity.java同級(jí)的目錄下新建一個(gè)java文件,導(dǎo)入必要的java包:

//android/app/src/main/java/com/lka/LinkingCustom.java
package com.lka;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;

其次辐马,創(chuàng)建CustomLinking類拷橘,你需要繼承ReactContextBaseJavaModule類,并實(shí)現(xiàn)getName函數(shù)喜爷。這里的getName函數(shù)是必須的冗疮,返回值就是你在js端通過(guò)NativeModules拿到的模塊名"LinkingCustom"一致:

public class LinkingCustom extends ReactContextBaseJavaModule {
    public LinkingCustom(ReactApplicationContext reactContext) {
        super(reactContext);
    }
    @Override
    public String getName() {
        return "LinkingCustom";
    }
}

然后,我們實(shí)現(xiàn)重置intent的函數(shù)檩帐,將其命名為resetURL:

...
@Override
public String getName() {
    return "LinkingCustom";
}
//必須添加@ReactMethod關(guān)鍵字才能在js側(cè)被調(diào)用
@ReactMethod
//不可以直接將結(jié)果return术幔,因?yàn)閖s側(cè)是異步獲取結(jié)果的,這里將結(jié)果返回成promise湃密,
public void resetURL(Promise promise) {
    try {
        Activity currentActivity = getCurrentActivity();
        if (currentActivity != null) {
            Intent intent = new Intent(Intent.ACTION_MAIN);
            currentActivity.setIntent(intent);
        }
        promise.resolve(true);
    } catch (Exception e) {
        promise.reject(new JSApplicationIllegalArgumentException("Could not reset URL"));
      }
}

LinkingCustomReactPackage

我們?cè)谕?jí)下新建LinkingCustomReactPackage.java文件诅挑,用來(lái)注冊(cè)模塊:

//android/app/src/main/java/com/lka/LinkingCustomReactPackage.java
package com.coomarts;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//必須實(shí)現(xiàn)ReactPackage接口和createNativeModules方法
public class LinkingCustomReactPackage implements ReactPackage{
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext){
        List<NativeModule> modules=new ArrayList<>();
        //在這里添加你想注冊(cè)的模塊
        modules.add(new LinkingCustom(reactContext));
        return modules;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules(){
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContent){
        return Collections.emptyList();
    }
}

為包管理添加實(shí)例

最后一步就是在MainApplication.java里添加實(shí)例,與添加第三方組件實(shí)例相同:

//android/app/src/main/java/com/lka/MainApplication.java
...
@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
        new SQLitePluginPackage(),
        new MainReactPackage(),
        new RNDeviceInfo(),
        new VectorIconsPackage(),
        new LinkingCustomReactPackage()
    );
}

大功告成勾缭,現(xiàn)在我們重復(fù)之前的步驟揍障,看一下運(yùn)行結(jié)果:

image.png

image.png

image.png

除非lka是由lkb喚起的,否則在其他情況下運(yùn)行l(wèi)ka得到的均是null值俩由。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毒嫡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子幻梯,更是在濱河造成了極大的恐慌兜畸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碘梢,死亡現(xiàn)場(chǎng)離奇詭異咬摇,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)煞躬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門肛鹏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人恩沛,你說(shuō)我怎么就攤上這事在扰。” “怎么了雷客?”我有些...
    開(kāi)封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵芒珠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我搅裙,道長(zhǎng)皱卓,這世上最難降的妖魔是什么裹芝? 我笑而不...
    開(kāi)封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮娜汁,結(jié)果婚禮上嫂易,老公的妹妹穿的比我還像新娘。我一直安慰自己存炮,他們只是感情好炬搭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著穆桂,像睡著了一般宫盔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上享完,一...
    開(kāi)封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天灼芭,我揣著相機(jī)與錄音,去河邊找鬼般又。 笑死彼绷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茴迁。 我是一名探鬼主播寄悯,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼堕义!你這毒婦竟也來(lái)了猜旬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤倦卖,失蹤者是張志新(化名)和其女友劉穎洒擦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體怕膛,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熟嫩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了褐捻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掸茅。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖柠逞,靈堂內(nèi)的尸體忽然破棺而出倦蚪,到底是詐尸還是另有隱情,我是刑警寧澤边苹,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站裁僧,受9級(jí)特大地震影響个束,放射性物質(zhì)發(fā)生泄漏慕购。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一茬底、第九天 我趴在偏房一處隱蔽的房頂上張望沪悲。 院中可真熱鬧,春花似錦阱表、人聲如沸殿如。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涉馁。三九已至,卻和暖如春爱致,著一層夾襖步出監(jiān)牢的瞬間烤送,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工糠悯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帮坚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓互艾,卻偏偏與公主長(zhǎng)得像试和,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纫普,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程阅悍,因...
    小菜c閱讀 6,358評(píng)論 0 17
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,510評(píng)論 25 707
  • 1.什么是Activity?問(wèn)的不太多溉箕,說(shuō)點(diǎn)有深度的 四大組件之一,一般的,一個(gè)用戶交互界面對(duì)應(yīng)一個(gè)activit...
    JoonyLee閱讀 5,728評(píng)論 2 51
  • Cloc簡(jiǎn)介 Cloc是一款使用Perl語(yǔ)言開(kāi)發(fā)的開(kāi)源代碼統(tǒng)計(jì)工具,支持多平臺(tái)使用悦昵、多語(yǔ)言識(shí)別肴茄,能夠計(jì)算指定目標(biāo)文...
    JxMY閱讀 3,281評(píng)論 0 0
  • 2017年對(duì)我來(lái)說(shuō)是特殊的,通過(guò)關(guān)注悠南的文章加入到了早起早睡這個(gè)大家庭里但指,認(rèn)識(shí)了來(lái)自各地各行的優(yōu)秀的朋友們寡痰,在我...
    不甘杺閱讀 266評(píng)論 0 0