背景:
?這篇文章用來記錄一些商戶支持過程中遇到的問題和解決思路,也算是對(duì)自己工作的一個(gè)記錄吧。
1.module模塊嵌入問題( AndroidManifest.xml合并沖突)
? ? 在集成SDK時(shí),若SDK與主module(通常為app)的AndroidManifest.xml發(fā)生合并沖突,嘗試在主module的Manifest文件中配置tools屬性。

2.標(biāo)題欄遮擋問題
? ? 商戶APP在加載我們的webview頁面時(shí),由于APP做了沉浸式處理,導(dǎo)致手機(jī)導(dǎo)航欄圖標(biāo)(電池,運(yùn)營商標(biāo)識(shí)等)遮擋了webview頂部的操作按鈕,影響用戶操作。
解決方式:
在AndroidManifest.xml文件中對(duì)activity的聲明添加
android:theme="@android:style/Theme.Light.NoTitleBar"
? ? ? 隱藏主工程APP設(shè)置的titlebar效果,使用系統(tǒng)titlebar,這樣頁面可以自動(dòng)下移不遮擋了,后來我在對(duì)接支付寶的時(shí)候,發(fā)現(xiàn)阿里也在自己的H5頁面配置上有加這一句,應(yīng)該也是考慮到這個(gè)因素。
3.橋接方法不生效問題(混淆規(guī)則)
在targetSdkVersion 17以上需要添加接口JavaScriptInterface才能用,即在JS調(diào)用的Java代碼上方加上@JavaScriptInterface注解,同時(shí),在混淆代碼時(shí),注意不要混淆JavascriptInterface的注解,否則會(huì)出現(xiàn)js不能調(diào)用java代碼的情況。
-keepattributes *Annotation*
-keepattributes *JavaScriptInterface*
4.返回頁面問題(activity啟動(dòng)模式)
??喚起銀行APP支付成功后,在成功頁點(diǎn)擊“返回商戶”按鈕,喚起商戶內(nèi)嵌回調(diào)activity,商戶想直接收起該頁面,返回自己的訂單頁,但finish 回調(diào)activity后,回到了手機(jī)銀行。
我們假定商戶APP為A,銀行APP為B,由A喚起B(yǎng)時(shí),由任務(wù)棧S1跳轉(zhuǎn)到新任務(wù)棧S2執(zhí)行,點(diǎn)擊“返回商戶”,雖然回到了商戶APP,

此時(shí)task還在S2中執(zhí)行,所以執(zhí)行完上述邏輯后,只是關(guān)閉了當(dāng)前頁面,并沒有回到商戶APP。
為什么還在S2執(zhí)行呢?理論上不同的activity應(yīng)該在不同的任務(wù)中執(zhí)行,但android在設(shè)計(jì)時(shí),考慮到無縫的用戶體驗(yàn),在喚起時(shí),會(huì)將不同的activity保存在相同的任務(wù)中,這樣看起來像是在一個(gè)應(yīng)用中執(zhí)行。
解決方案:
1. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent添加喚起方式配置
2. 修改回調(diào)activity的啟動(dòng)類型,由singleTop改為singleTask。
<activity
android:name=".icbcPay.PayResultHandler"
android:exported="true"
android:launchMode="singleTask">
</activity>
這兩種方式是一樣的,以singleTask方式啟動(dòng)后,系統(tǒng)會(huì)嘗試尋找與目標(biāo)activity相關(guān)聯(lián)的任務(wù),并嘗試在該任務(wù)中啟動(dòng)。
微信支付的回調(diào)activity配置的是“singleTop”,猜測(cè)是intent啟動(dòng)的時(shí)候做了配置,不然感覺也會(huì)出這種問題。
關(guān)于activity的啟動(dòng)模式(任務(wù)和返回棧)
? ?(1)standard 默認(rèn)啟動(dòng)模式,不管有沒有已經(jīng)存在的實(shí)例,都會(huì)生成新的實(shí)例
? ?(2)singleTop 跳轉(zhuǎn)時(shí)系統(tǒng)會(huì)現(xiàn)在棧結(jié)構(gòu)中尋找是否有一個(gè)activity實(shí)例位于棧頂,如果有,則不再生成新的實(shí)例。如果沒有,或者有但不在棧頂,則生成新的實(shí)例。
? ?(3)singleTask 啟動(dòng)新任務(wù)執(zhí)行,在啟動(dòng)activity時(shí),若有一個(gè)運(yùn)行著這個(gè)activity的task,那這個(gè)activity實(shí)例會(huì)被調(diào)到前臺(tái),處于該activity之上的其他頁面會(huì)被清掉。
? ? ? (4) singleInstance?開辟一個(gè)只允許一個(gè)activity實(shí)例在里面運(yùn)行的task. 如果用同樣的intent再次啟動(dòng)這個(gè)activity,task會(huì)被調(diào)到前臺(tái),其Activity.onNewIntent()?會(huì)被調(diào)用. 如果這個(gè)activity實(shí)例要啟動(dòng)一個(gè)新activity,那么這個(gè)新activity會(huì)在一個(gè)新task中運(yùn)行.
? ? ? ?singleTop適合接收通知啟動(dòng)的內(nèi)容顯示頁面。例如,某個(gè)新聞客戶端的新聞內(nèi)容頁面,如果收到10個(gè)新聞推送,每次都打開一個(gè)新聞內(nèi)容頁面是很煩人的。
? ? ? ? singleTask適合作為程序入口點(diǎn)。例如瀏覽器的主界面。不管從多少個(gè)應(yīng)用啟動(dòng)瀏覽器,只會(huì)啟動(dòng)主界面一次,其余情況都會(huì)走onNewIntent,并且會(huì)清空主界面上面的其他頁面。之前打開過的頁面,打開之前的頁面就ok,不再新建。
? ? ? ? ?singleInstance適合需要與程序分離開的頁面。例如鬧鈴提醒,將鬧鈴提醒與鬧鈴設(shè)置分離。singleInstance不要用于中間頁面,如果用于中間頁面,跳轉(zhuǎn)會(huì)有問題,比如:A -> B (singleInstance) -> C,完全退出后,在此啟動(dòng),首先打開的是B。
? ? ? ?當(dāng)ActivityA的LaunchMode為SingleTop時(shí),如果ActivityA在棧頂,且現(xiàn)在要再啟動(dòng)ActivityA,這時(shí)會(huì)調(diào)用onNewIntent()方法?
? ? ? 當(dāng)ActivityA的LaunchMode為SingleInstance,SingleTask時(shí),如果已經(jīng)ActivityA已經(jīng)在堆棧中,那么此時(shí)會(huì)調(diào)用onNewIntent()方法?
? ? ? ?當(dāng)ActivityA的LaunchMode為Standard時(shí),由于每次啟動(dòng)ActivityA都是啟動(dòng)新的實(shí)例,和原來啟動(dòng)的沒關(guān)系,所以不會(huì)調(diào)用原來ActivityA的onNewIntent方法
5.回調(diào)路徑找不到(app packageName與applicationId的關(guān)系)
? ? ?SDK回調(diào)路徑由app的packageName+固定名稱activity組成,通常我們?cè)诠こ汤?,如果在build.gradle里指定applicationId,那么getPackageName獲得的是applicationId,否則才是AndroidManifest.xml 配置的 packageName,AndroidManifest.xml里配置的packageName與工程路徑一致。applicationId可以視為應(yīng)用的唯一標(biāo)識(shí)符,運(yùn)行ID,上架之后不能再改變。
? ? ?假設(shè)我們?cè)贏ndroidManifest.xml里配置的是path1,applicationId是path2,那么通過getPackageName()獲得的是path2,回調(diào)路徑是path2+activity name,而這個(gè)路徑,在工程下是找不到的。
? ? 這個(gè)問題最后沒有想到合適的方法的解決,當(dāng)時(shí)是客戶硬在工程路徑下,創(chuàng)建了path2+activity文件解決的,微信也是通過packageName+activity來做回調(diào)的,有環(huán)境了試試會(huì)不會(huì)有問題。如果沒問題,他們是怎么拿到正確的工程路徑呢……
2018.6.6
?這個(gè)問題我今天想起來了。。。用activity-alias 控制目標(biāo)跳轉(zhuǎn)路徑可以實(shí)現(xiàn),在主工程AndroidManifest.xml中,聲明path2+activity,配置activity-alias,path1的targetActivity指向path2.
2018.6.13
其實(shí)還是要新建path2+activity文件,這個(gè)方法行不通
6.Toast導(dǎo)致的閃退問題
出現(xiàn)“訂單數(shù)據(jù)不完整”這種場(chǎng)景時(shí),SDK引起了宿主APP的crash。
原因是該Toast在AsyncTask的doInBackground中顯示,這段代碼執(zhí)行在非主線程中且未初始化Looper。
Toast一定要運(yùn)行在主線程(UI線程)嗎?
? ? ? 其實(shí)不是的,看了Toast的源碼后,其實(shí)Toast也是利用了進(jìn)程間通訊,其構(gòu)造函數(shù)里需傳入looper。而Activity和service之所以不需要初始化looper,是因?yàn)橹骶€程(ActivityThread)初始化了looper。
private void showMessage(final Context context, final String msg){
new Thread(){
public void run(){
Looper.prepare();
Toast.makeText(context,msg,Toast.LENGTH_SHORT).show();
Looper.loop();
}}.start();
}
Looper會(huì)一直死循環(huán),占用內(nèi)存,該線程不會(huì)被釋放,新起線程執(zhí)行,建議還是用Handler的方式去實(shí)現(xiàn)。
? ? ? ? Handler?handler?=?new?Handler(Looper.getMainLooper());??????????
? ? ? ? handler.post(new?Runnable()?{??????????????
? ? ? ?@Override??????????????
? ? ? ? public?void?run()?{?????????????????
? ? ? ? ?Toast?toast?=?Toast.makeText(context,?text,?duration);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?toast.show();??????????????}??????????});??
UI操作一定要在UI線程里執(zhí)行嗎?
Toast可能是一個(gè)特例,其實(shí)UI操作不一定要在UI線程里執(zhí)行。之所以子線程不能更新界面,是因?yàn)锳ndroid在線程的方法里面采用checkThread進(jìn)行判斷是否是主線程,而這個(gè)方法是在ViewRootImpl中的,這個(gè)類是在onResume里面才生成的,因此,如果這個(gè)時(shí)候子線程在onCreate里面生成更新UI,而且沒有做阻塞,就是耗時(shí)多的操作,還是可以更新UI的。