問題背景
在開發(fā) PassportSDK 時遇到的此類問題,測試反饋說當(dāng)打開 App 進入登錄頁面,此時如果切換出去到手機設(shè)置頁面將App 的定位權(quán)限設(shè)置為「拒絕授予」,在切換回 App 會發(fā)生登錄信息完全正確也登錄不上的情況。
問題分析
經(jīng)過抓包及打斷點查找問題根源,發(fā)現(xiàn)是用戶在操作應(yīng)用權(quán)限授予時,App 對象及 Activity 經(jīng)過了銷毀重建,此時一些基礎(chǔ)的 UI 狀態(tài)數(shù)據(jù)可以通過 onSaveInstanceBundle 方法進行保存,在 onCreate 方法中取出保存的狀態(tài)進行恢復(fù)。但是登陸成功的回調(diào) loginListener 由于無法序列化且是隨著啟動視圖的方法設(shè)置進來的,所以無法恢復(fù)。這造成系統(tǒng)恢復(fù)的 Activity 對象持有的回調(diào) loginListener 為空,所以即使用戶輸入的登陸信息完全正確也無法登陸。
問題解決及帶來的思考
今后的開發(fā)中,要避免這種無法序列化的回調(diào)類實例等與啟動視圖的方法綁定的 Case,例如,PassportSDK 中啟動登錄頁面的方法如下:
public void login(int loginType, MCLoginListener listener) {
LoginActivity.setLoginListener(listener);
Intent intent = new Intent(Global.getContext(), LoginActivity.class);
intent.putExtra(LOGIN_TYPE, loginType);
Global.getContext().startActivity(intent);
}
可以看到上面的方法中,我們將設(shè)置登錄回調(diào)的步驟放在了啟動視圖的方法中。這在平時并沒有什么問題,我們只需要在某個按鈕的點擊事件中調(diào)用此方法,傳入一個回調(diào),即可完成正常的業(yè)務(wù)邏輯。修改之前的代碼邏輯如下:
public class ActivityDemo extend Activity {
MCLoginListener mListener = new MCLoginListener() {
public void onSuccess(){
//TODO
}
public void onError(){
//TODO
}
}
public void onCreate(Bundle savedInstanceBundle) {
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(v -> {
login(Global.CODE_PSD_LOGIN, mListener);
})
}
}
但是假如 App 經(jīng)過上述的權(quán)限更改、屏幕旋轉(zhuǎn)、在后臺時間過久等場景,導(dǎo)致 App 對象及 Activity 銷毀重建時,由于重建的 Activity 不是通過上述的啟動視圖的方法展示的,并且在 Activity 中也無法將回調(diào)類對象序列化并恢復(fù),所以我們以后一定要記住需要將回調(diào)與啟動視圖的方法分離開來,并在上文對象(上個頁面 Activity 對象或 App 對象)的 onCreate 生命周期中重新調(diào)用設(shè)置回調(diào)的方法即可。修改之后的代碼如下:
public class ActivityDemo extend Activity {
MCLoginListener mListener = new MCLoginListener() {
public void onSuccess(){
//TODO
}
public void onError(){
//TODO
}
}
public void onCreate(Bundle savedInstanceBundle) {
// 放在這里可以保證系統(tǒng)恢復(fù)此上文 Activity 時就為 LoginActivity 添加了回調(diào)類實例
LoginActivity.setLoginListener(listener);
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(v -> {
login(Global.CODE_PSD_LOGIN);
})
}
// 只保留純粹的啟動視圖方法
public void login(int loginType) {
Intent intent = new Intent(Global.getContext(), LoginActivity.class);
intent.putExtra(LOGIN_TYPE, loginType);
Global.getContext().startActivity(intent);
}
}