android 權(quán)限改造,讓你的權(quán)限更加合理

前言:

好久沒(méi)更新文章了成艘,距離上次更新已經(jīng)有5個(gè)月時(shí)間了赏半,前段時(shí)間結(jié)合自己在處理的權(quán)限方面的問(wèn)題寫了這么篇水文,希望能幫到其他人淆两。同時(shí)還是比較佩服那些高產(chǎn)的作者断箫,在兼顧工作的同時(shí)還能持續(xù)輸出優(yōu)秀的技術(shù)文章。

感慨下秋冰,回到正題仲义,本文主要要說(shuō)的是在權(quán)限請(qǐng)求體驗(yàn)上的一些優(yōu)化,并非權(quán)限適配方面的文章剑勾,角度上和目前市面上的權(quán)限文章可能不太一樣埃撵。
文章主要是針對(duì)目前公司app在權(quán)限請(qǐng)求使用上的一些改進(jìn)與優(yōu)化,大致可以分為三個(gè)部分
(1)權(quán)限庫(kù)權(quán)限回調(diào)bug修復(fù)
(2)系統(tǒng)權(quán)限請(qǐng)求彈框重疊解決
(3)系統(tǒng)權(quán)限開(kāi)關(guān)引起的app進(jìn)程殺死重啟

(1)權(quán)限庫(kù)權(quán)限請(qǐng)求回調(diào)

其實(shí)目前市面上有不少高質(zhì)量的權(quán)限請(qǐng)求庫(kù)可以使用虽另,幾行代碼就能幫助我們完成權(quán)限的請(qǐng)求暂刘,但說(shuō)來(lái)比較有意思,不管是我目前公司還是之前公司對(duì)于權(quán)限請(qǐng)求這塊的處理洲赵,似乎更加偏向于自己去實(shí)現(xiàn)類似的庫(kù)鸳惯。

其實(shí)不管是自己造輪子還是使用三方開(kāi)源庫(kù)商蕴,只要能做到對(duì)于其中的原理掌握都是可取的,使用三方庫(kù)更多的優(yōu)勢(shì)在于敏捷開(kāi)發(fā)芝发,節(jié)約時(shí)間成本绪商,但是使用三方庫(kù)一個(gè)比較明顯的缺點(diǎn)就是可定制化方面確實(shí)弱了點(diǎn),尤其是在你沒(méi)有完全摸清開(kāi)源庫(kù)代碼的情況下辅鲸,貿(mào)然的修改可能會(huì)引起各種問(wèn)題格郁。

扯的有點(diǎn)跑題了,回到權(quán)限問(wèn)題上來(lái)独悴,如果是自己去實(shí)現(xiàn)一個(gè)權(quán)限庫(kù)例书,其中一個(gè)問(wèn)題肯定是繞不開(kāi)的,那就是權(quán)限請(qǐng)求結(jié)果回調(diào)如何處理的問(wèn)題刻炒。Activity通過(guò)

  @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)

方法處理權(quán)限請(qǐng)求的結(jié)果决采,那么問(wèn)題來(lái)了實(shí)現(xiàn)權(quán)限庫(kù)必然需要在

<font color=red size=50>onRequestPermissionsResult</font>

去處理請(qǐng)求,然后將處理結(jié)果通過(guò)callback的形式進(jìn)行回調(diào)坟奥。之前大致研究過(guò)個(gè)別開(kāi)源權(quán)限庫(kù)树瞭,主要做法大概有三種
(1)在項(xiàng)目的BaseActivity中復(fù)寫onRequestPermissionsResult方法,通過(guò)調(diào)用權(quán)限庫(kù)的處理方法實(shí)現(xiàn)回調(diào)爱谁,類似

  @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        PermissionManager.getInstance(this).onRequestPermissionsResult(requestCode,permissions,grantResults);
    }

這種方案簡(jiǎn)單有效晒喷,也是最容易想到的方案,但是對(duì)于一個(gè)權(quán)限庫(kù)访敌,還需要入侵到BaseActivity中去做一些事情凉敲,個(gè)人覺(jué)得多少差點(diǎn)意思,你都是一個(gè)成熟的權(quán)限庫(kù)寺旺,要時(shí)候?qū)W會(huì)自己處理權(quán)限回調(diào)問(wèn)題了
(2)開(kāi)啟一個(gè)透明的activity爷抓,然后在該activity中得到回調(diào)的結(jié)果,通過(guò)PermissionManager傳入的callback實(shí)現(xiàn)回調(diào)這種方案也是比較容易想到的阻塑,也是一種可行的方案废赞,目前公司就是采用這種方案,但是同事在實(shí)現(xiàn)上存在一個(gè)bug叮姑,這個(gè)等下說(shuō)到
(3)通過(guò)給Activity添加一個(gè)fragment來(lái)實(shí)現(xiàn)權(quán)限結(jié)果的回調(diào)
這個(gè)方案是我認(rèn)為最為巧妙的實(shí)現(xiàn),通過(guò)fragment上同名的方法對(duì)回調(diào)進(jìn)行處理据悔。這其實(shí)就是對(duì)于fragment的巧妙利用传透,jetpack在實(shí)現(xiàn)liftcycle也是使用的類似的技巧。感興趣的可以自行搜索相關(guān)文章极颓。

回到文章說(shuō)的第二種實(shí)現(xiàn)上朱盐,一般我們的權(quán)限請(qǐng)求使用方式都是類似這種形式(不包括利用apt實(shí)現(xiàn)的注解形式)

PermissionManager.getInstance(context,permissionItems).checkPermission(new PermissionCallback() {
                @Override
                public void onGuaranteed(String permission) {
                    ...
                }

                @Override
                public void onDenied(String permission, boolean shouldShowAgain) {
                    ...
                }

                @Override
                public void onFinished(boolean isAllGuaranteed) {
                    ...
                }
            });
如果是開(kāi)啟透明Activity處理權(quán)限請(qǐng)求,必須將permissionCallback傳遞給透明Activity菠隆,公司內(nèi)部權(quán)限庫(kù)在處理這塊時(shí)兵琳,采用了簡(jiǎn)單粗暴的方式

···
RequestPermissionActivity.setCallback(permissionCallback)
···
RequestPermissionActivity就是文章所說(shuō)的透明Activity狂秘,通過(guò)靜態(tài)方法將permissionCallback保存為Activity的靜態(tài)變量,然后在Activity銷毀的時(shí)候調(diào)用

    @Override
    protected void onDestroy() {
        super.onDestroy();
        permissionCallback = null;
    }

避免內(nèi)存的泄露問(wèn)題躯肌,看起來(lái)簡(jiǎn)單有效者春,但是作為一個(gè)static類型的callback,當(dāng)同時(shí)單獨(dú)發(fā)起兩個(gè)權(quán)限請(qǐng)求就會(huì)導(dǎo)致后一個(gè)callback覆蓋掉前一個(gè)callback清女。這種場(chǎng)景在目前公司的業(yè)務(wù)環(huán)境是有可能出現(xiàn)的钱烟,比如最常見(jiàn)的在賬號(hào)登錄之后會(huì),由于業(yè)務(wù)關(guān)系會(huì)發(fā)起一次數(shù)據(jù)校驗(yàn)請(qǐng)求嫡丙,等到校驗(yàn)正確后才會(huì)發(fā)起定位權(quán)限請(qǐng)求拴袭,如果網(wǎng)絡(luò)存在延遲的情況下在數(shù)據(jù)返回之前直接切換到其他需要權(quán)限的頁(yè)面就會(huì)導(dǎo)致callback覆蓋問(wèn)題。

問(wèn)題說(shuō)明白之后剩下的解決思路就清晰了曙博,禁止將callback設(shè)置為activity的靜態(tài)變量拥刻,通過(guò)LocalBroadcastManager來(lái)實(shí)現(xiàn)透明Activity和PermissionManager之間的通信,解決方式參考如下
PermissionManager部分相關(guān)代碼:

    private void startRequest(PermissionCallback callback) {
        //給PermissionManager注冊(cè)一個(gè)本地廣播
        InnerBroadcastReceiver receiver = new InnerBroadcastReceiver(callback, mId);
        LocalBroadcastManager.getInstance(mContext)
                .registerReceiver(receiver, new IntentFilter(PERMISSION_CALLBACK));
        Intent intent = new Intent(mContext, RequestPermissionActivity.class);
        ...
        intent.putExtra(RequestPermissionActivity.INTENT_KEY_DATA, permissionParcelable);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);
        ...
    }

    private static class InnerBroadcastReceiver extends BroadcastReceiver {

        PermissionCallback callback;
        int mId;

        InnerBroadcastReceiver(PermissionCallback callback, int id) {
            this.callback = callback;
            this.mId = id;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null) {
                return;
            }
            int id = intent.getIntExtra(ID, -1);
            if (id != mId) {
                return;
            }
            boolean unregister = intent.getBooleanExtra(UNREGISTER, false);
            //Activity銷毀時(shí)需要及時(shí)注銷掉對(duì)應(yīng)廣播父泳,防止內(nèi)存泄露
            if (unregister) {
                LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
            } else if (callback != null) {
                boolean finish = intent.getBooleanExtra(CHECK_FINISH, false);
                if (finish) {
                    boolean isAllGuaranteed = intent.getBooleanExtra(IS_ALL_GUARANTEED, false);
                    callback.onFinished(isAllGuaranteed);
                } else {
                    boolean approve = intent.getBooleanExtra(APPROVE, false);
                    String name = intent.getStringExtra(PERMISSION_NAME);
                    boolean shouldShowAgain = intent.getBooleanExtra(SHOULD_SHOW_AGAIN, true);
                    if (approve) {
                        //將透明Activity中的callback移到InnerBroadcastReceiver內(nèi)部
                        callback.onGuaranteed(name);
                    } else {
                        callback.onDenied(name, shouldShowAgain);
                    }
                }
            }
        }
    }

省略了部分和權(quán)限庫(kù)具體實(shí)現(xiàn)相關(guān)的代碼般哼,不影響整體思路的理解。在startRequest發(fā)起權(quán)限請(qǐng)求時(shí)先注冊(cè)一個(gè)本地廣播尘吗,廣播的接收在onReceive中處理逝她,主要就是分析返回的數(shù)據(jù)來(lái)決定調(diào)用onGuaranteed還是onDenied。本地廣播被注冊(cè)成功后睬捶,必須有一個(gè)廣播的發(fā)送者黔宛,透明Activity就是這么一個(gè)角色。

透明Activity部分代碼如下:

    private void sendPermissionSignal(String permission, boolean approve, boolean shouldShowAgain) {
        Intent intent = new Intent();
        intent.setAction(PERMISSION_CALLBACK);
        intent.putExtra(ID, mId);
        intent.putExtra(APPROVE, approve);
        intent.putExtra(PERMISSION_NAME, permission);
        intent.putExtra(SHOULD_SHOW_AGAIN, shouldShowAgain);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    private void onGuarantee(String permission) {
        sendPermissionSignal(permission, true, true);
    }

    private void onDeny(String permission, boolean shouldShowAgain) {
        sendPermissionSignal(permission, false, shouldShowAgain);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Intent intent = new Intent();
        intent.putExtra(UNREGISTER, true);
        intent.putExtra(ID, mId);
        intent.setAction(PERMISSION_CALLBACK);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

在透明Activity中權(quán)限請(qǐng)求的結(jié)果通過(guò)sendPermissionSignal通知到PermissionManager擒贸,同時(shí)在onDestroy時(shí)反注冊(cè)掉對(duì)應(yīng)的本地廣播臀晃,經(jīng)上述代碼處理后即可解決callback覆蓋問(wèn)題。

(2)系統(tǒng)權(quán)限請(qǐng)求框重疊問(wèn)題

直接上圖秒懂


797092AD-73C6-4c70-A576-61BA3075F9F9.png

可以看出出現(xiàn)了兩個(gè)系統(tǒng)權(quán)限框重疊的現(xiàn)象介劫,當(dāng)同時(shí)有兩個(gè)權(quán)限單獨(dú)發(fā)起權(quán)限請(qǐng)求時(shí)就會(huì)導(dǎo)致這種情況徽惋,比如兩個(gè)不同的組件模塊同時(shí)發(fā)起權(quán)限請(qǐng)求,雖然不影響app的正常運(yùn)行座韵,但看到這種彈框重疊總覺(jué)得app顯得有點(diǎn)low故源,作為一個(gè)有追求的開(kāi)發(fā)者肯定要解決掉這個(gè)問(wèn)題讳癌,主要思路就是通過(guò)一個(gè)管理類來(lái)統(tǒng)一調(diào)用app中的所有權(quán)限,類似于AsyncTask中默認(rèn)串行執(zhí)行任務(wù)行為,只有當(dāng)一個(gè)任務(wù)執(zhí)行完畢之后才會(huì)繼續(xù)執(zhí)行下一個(gè)任務(wù)楣责。
具體關(guān)于PermissionDispatcher實(shí)現(xiàn)如下:

    public class PermissionDispatcher {

    private volatile static PermissionDispatcher dispatcher;
    private List<Task> tasks;//每次權(quán)限請(qǐng)求封裝成一個(gè)task
    private Handler handler;
    private Context context;

    private PermissionDispatcher(Context context) {
        handler = new Handler(Looper.getMainLooper());//UI線程執(zhí)行任務(wù)垦缅,避免多線程并發(fā)可能導(dǎo)致的問(wèn)題
        tasks = new LinkedList<>();
        this.context = context.getApplicationContext();
    }

    public static PermissionDispatcher getInstance(Context context) {
        if (dispatcher == null) {
            synchronized (PermissionDispatcher.class) {
                if (dispatcher == null) {
                    dispatcher = new PermissionDispatcher(context);
                }
            }
        }
        return dispatcher;
    }

    public void checkPermissions(final boolean showCustomDialog, final boolean autoNext, final List<PermissionItem> items, final PermissionCallback callback) {
    handler.post(new Runnable() {

        @Override
        public void run() {
            PermissionDispatcher.Task task = new PermissionDispatcher.Task(items, showCustomDialog, callback, autoNext);
            tasks.add(task);
            if (tasks.size() == 1) {
                PermissionDispatcher.Task current = tasks.get(0);
                current.execute();
            }
        }
    });
}

public void checkPermissions(final boolean showCustomDialog, final List<PermissionItem> items, final PermissionCallback callback) {
        checkPermissions(showCustomDialog, true, items, callback);
    }

    public void checkPermission(final boolean showCustomDialog, boolean autoNext, final PermissionItem item, final PermissionCallback callback) {
        List<PermissionItem> list = Collections.singletonList(item);
        checkPermissions(showCustomDialog, autoNext, list, callback);
    }

    public void checkPermission(final boolean showCustomDialog, final PermissionItem item, final PermissionCallback callback) {
        List<PermissionItem> list = Collections.singletonList(item);
        checkPermissions(showCustomDialog, true, list, callback);
    }

private class Task {
    private List<PermissionItem> items;
    private PermissionCallback callback;
    private boolean showCustomDialog;

    private boolean complete;       //表示task是否執(zhí)行完畢
    private boolean autoNext;

    private Task(List<PermissionItem> items, boolean showCustomDialog
            , PermissionCallback callback, boolean autoNext) {
        this.items = items;
        this.showCustomDialog = showCustomDialog;
        this.callback = callback;
        this.autoNext = autoNext;
    }

    private void execute() {
        PermissionManager instance = PermissionManager.getInstance(context, showCustomDialog);
        if (items != null && !items.isEmpty()) {
            for (PermissionItem item : items) {
                instance.addPermission(item);
            }
        }
        instance.checkPermission(new PermissionCallback() {
            @Override
            public void onGuaranteed(String permission) {
                complete = true;
                callback.onGuaranteed(permission);
            }

            @Override
            public void onDenied(String permission, boolean shouldShowAgain) {
//                    Log.e("mandy", "dispatcher onDenied");
                complete = true;
                callback.onDenied(permission, shouldShowAgain);
            }

            @Override
            public void onFinished(boolean isAllGuaranteed) {
//                    Log.e("mandy", "onFinished???");
                callback.onFinished(isAllGuaranteed);
                if (autoNext) {
                    nextInternal();
                }
            }
        });
    }
}

    private void nextInternal() {
        if (tasks.isEmpty()) {
            return;
        }
        tasks.remove(0);
        if (!tasks.isEmpty()) {
            PermissionDispatcher.Task task = tasks.get(0);
            task.execute();
        }
    }

    public void next() {
        if (tasks.isEmpty()) {
            return;
        }
        PermissionDispatcher.Task current = tasks.get(0);
        if (current.autoNext) {
            return;
        }
        if (current.complete) {
            nextInternal();
        }
    }
}

具體的實(shí)現(xiàn)就是上述代碼了确憨,當(dāng)每次發(fā)起權(quán)限請(qǐng)求時(shí)都會(huì)封裝成task国撵,將該task保存到tasks容器內(nèi)部,當(dāng)task是容器中的第一個(gè)元素時(shí)會(huì)去執(zhí)行task的excute方法成黄,如果不是第一個(gè)元素則等待被調(diào)用呐芥。當(dāng)task執(zhí)行完畢之后就會(huì)從容器中被移除然后繼續(xù)執(zhí)行下一個(gè)task逻杖。

唯一需要特別說(shuō)明的就是autoNext這個(gè)變量,一般情況下當(dāng)一個(gè)task執(zhí)行完畢后會(huì)自動(dòng)繼續(xù)執(zhí)行下一個(gè)task這種情況下autoNext為true思瘟,但當(dāng)你請(qǐng)求的權(quán)限會(huì)調(diào)用起系統(tǒng)的一些頁(yè)面時(shí)情況會(huì)復(fù)雜一些荸百,比如拍照請(qǐng)求攝像頭權(quán)限成功后手機(jī)會(huì)調(diào)用系統(tǒng)相機(jī)頁(yè)面,如果這時(shí)PermissionDispatcher中還有未執(zhí)行的task潮太,當(dāng)該task被取出執(zhí)行時(shí)就會(huì)導(dǎo)致出現(xiàn)權(quán)限彈框覆蓋系統(tǒng)頁(yè)面的問(wèn)題管搪,如圖
pic.jpg

用戶正在拍著照,突然畫面中間彈個(gè)一個(gè)權(quán)限請(qǐng)求铡买,是不是顯得有點(diǎn)突兀更鲁,在使用體驗(yàn)上是不是略差。理解上述場(chǎng)景之后就可以明白這種情況下就不能將autoNext設(shè)置為true奇钞,需要等拍照結(jié)束調(diào)用onActivityResult之后再繼續(xù)執(zhí)行PermissionDispatcher中剩余的task澡为,這就是autoNext字段的具體作用。當(dāng)autoNext設(shè)置為false時(shí)就不會(huì)主動(dòng)調(diào)用下一個(gè)需要執(zhí)行的task景埃,這時(shí)候就需要通過(guò)手動(dòng)調(diào)用PermissionDispatcher的next方法觸發(fā)下一個(gè)task的執(zhí)行媒至。

需要特別說(shuō)明下的是,這個(gè)權(quán)限請(qǐng)求彈框覆蓋系統(tǒng)頁(yè)面問(wèn)題不是引入PermissionDispatcher才導(dǎo)致的谷徙,一般的app如果沒(méi)做特殊處理都是有可能存在這個(gè)問(wèn)題的拒啰。

(3)系統(tǒng)權(quán)限開(kāi)關(guān)導(dǎo)致的app進(jìn)程殺死問(wèn)題

這也是權(quán)限請(qǐng)求處理過(guò)程中避不開(kāi)的一個(gè)坑,復(fù)現(xiàn)場(chǎng)景很簡(jiǎn)單當(dāng)你在手機(jī)系統(tǒng)設(shè)置頁(yè)面關(guān)閉app中某一個(gè)權(quán)限時(shí)完慧,就會(huì)導(dǎo)致你的app進(jìn)程被殺死谋旦!,這時(shí)候你再回到app頁(yè)面,系統(tǒng)會(huì)重走Activity的生命周期方法重建該Activity屈尼,這里會(huì)導(dǎo)致的一個(gè)問(wèn)題就是Activity中一些字段由于進(jìn)程被殺死時(shí)得不到保存而導(dǎo)致?tīng)顟B(tài)錯(cuò)誤册着,嚴(yán)重的可能導(dǎo)致頁(yè)面恢復(fù)時(shí)出現(xiàn)app崩潰問(wèn)題。

這里可以參考微信的做法脾歧,當(dāng)在系統(tǒng)設(shè)置中關(guān)閉權(quán)限回到app時(shí)讓app進(jìn)行重啟操作甲捏,重啟的代碼如下:

    private static void restart(Activity activity) {
        final Intent intent = new Intent();
        intent.setClassName(activity, sSplashPage);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        activity.startActivity(intent);
        activity.overridePendingTransition(0, 0);
    }

sSplashPage即為啟動(dòng)頁(yè)的全名,通過(guò)startActivity重走啟動(dòng)頁(yè)鞭执,并通過(guò)FLAG_ACTIVITY_CLEAR_TASK清除掉棧中其余的activity司顿,達(dá)到重啟app的目的

剩下的一個(gè)問(wèn)題就是如何判斷app需要重啟,顯然必須是權(quán)限在系統(tǒng)設(shè)置中被關(guān)閉這時(shí)回到app頁(yè)面才需要觸發(fā)重啟兄纺。進(jìn)程被殺死然后恢復(fù)Activity一個(gè)顯著的特點(diǎn)就是savedInstanceState不為null免猾,但是僅僅根據(jù)savedInstanceState不為null就判斷app需要重啟顯然是不靠譜的。app橫豎屏切換就是典型的應(yīng)用場(chǎng)景囤热,難道你要每次橫豎屏切換都重啟app?

關(guān)于系統(tǒng)權(quán)限關(guān)閉導(dǎo)致app進(jìn)程殺死获三,網(wǎng)上有一篇文章可以參考http://www.reibang.com/p/cb68ca511776旁蔼,文章底部留言有人指出判斷重啟的方案锨苏,

{1E653EB9-A0DA-4481-9CE3-9C7BECB62066}_20200511164212.jpg

但是經(jīng)過(guò)自己實(shí)測(cè)發(fā)現(xiàn)該思路并不可行,如公司的測(cè)試三星手機(jī)棺聊,在系統(tǒng)中權(quán)限關(guān)閉后會(huì)直接觸發(fā)application的onCreate方法伞租,而華為手機(jī)的application的onCreate方法會(huì)在你點(diǎn)擊app時(shí)才會(huì)執(zhí)行。這種差異就注定了留言中的方案不可行限佩。

這里提供一下自己解決這個(gè)問(wèn)題的思路葵诈,尋找app每次正常啟動(dòng)必經(jīng)的activity,如果檢測(cè)到該activity就認(rèn)為app是正常啟動(dòng) 祟同,之后即使出現(xiàn)savedInstanceState不為null也不觸發(fā)重啟作喘,這種必經(jīng)的activity一般都為splashActivity,特殊一點(diǎn)的如通過(guò)通知欄進(jìn)入app或者通過(guò)deeplink形式進(jìn)入app晕城,可以這個(gè)必經(jīng)Activity會(huì)變成一個(gè)中間過(guò)渡的Activity泞坦,但是思路上是和SplashActivity是一樣的。
如果app進(jìn)程被殺死然后恢復(fù)app頁(yè)面這種情況會(huì)直接跳過(guò)該splashActivity砖顷,從而就可以觸發(fā)app的重啟邏輯了贰锁。

根據(jù)以上思路便可以得到如下代碼:

   public class RestartWatcher {

    private static String sSplashPage = null;
    private static List<String> sWhiteList;
    private static boolean sRestartDisable;

    private static void restart(Activity activity) {
        final Intent intent = new Intent();
        intent.setClassName(activity, sSplashPage);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        activity.startActivity(intent);
        activity.overridePendingTransition(0, 0);
    }

    static void watch(Activity activity, Bundle savedInstanceState) {
        try {
            if (sSplashPage == null) {
                sSplashPage = getLauncherActivityName(activity.getApplication());
            }
            if (TextUtils.isEmpty(sSplashPage) || savedInstanceState == null) {
                return;
            }
            String name = activity.getClass().getCanonicalName();
            if (TextUtils.isEmpty(name)) {
                return;
            }
            if (sWhiteList != null && sWhiteList.contains(name)) {
                return;
            }
            //noinspection ConstantConditions
            if (name.equalsIgnoreCase(sSplashPage)) {
                sRestartDisable = true;
            }
            if (!sRestartDisable) {
                restart(activity);
            }
        } catch (Exception e) {
            e.printStackTrace();
            LogUtil.e("", "restart fail");
        }
    }

    private static String getLauncherActivityName(Application application) {
        Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.setPackage(application.getPackageName());
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        // 通過(guò)查詢,獲得所有ResolveInfo對(duì)象.
        List<ResolveInfo> resolveInfos = application.getPackageManager()
                .queryIntentActivities(mainIntent, 0);
        if (resolveInfos != null && !resolveInfos.isEmpty()) {
            return resolveInfos.get(0).activityInfo.name;
        }
        return "";
    }
}
}

通過(guò)如下代碼進(jìn)行注冊(cè)

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                RestartWatcher.watch(activity, savedInstanceState);
            }
            ...
}

因?yàn)閍pp重啟本質(zhì)上是和權(quán)限設(shè)置相關(guān)滤蝠,所以可以考慮將這部分的代碼挪到權(quán)限庫(kù)當(dāng)中豌熄。

總結(jié):

到此就將自己在改造app權(quán)限時(shí)遇到的三個(gè)問(wèn)題都闡述完畢了,除了第一個(gè)問(wèn)題可能和公司內(nèi)部權(quán)限庫(kù)實(shí)現(xiàn)相關(guān)外物咳,剩下的兩個(gè)問(wèn)題應(yīng)該是比較共性的問(wèn)題锣险。文章分享了自己在解決上述問(wèn)題時(shí)的思路,不一定是最合理的方案所森,但是可以給其他人一些參考囱持。

堅(jiān)持更新不容易,如果文章對(duì)你有一些幫助焕济,點(diǎn)個(gè)贊就是對(duì)我最大的支持

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纷妆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晴弃,更是在濱河造成了極大的恐慌掩幢,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件上鞠,死亡現(xiàn)場(chǎng)離奇詭異际邻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)芍阎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門世曾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人谴咸,你說(shuō)我怎么就攤上這事轮听∑叮” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵血巍,是天一觀的道長(zhǎng)萧锉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)述寡,這世上最難降的妖魔是什么柿隙? 我笑而不...
    開(kāi)封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮鲫凶,結(jié)果婚禮上禀崖,老公的妹妹穿的比我還像新娘。我一直安慰自己掀序,他們只是感情好帆焕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著不恭,像睡著了一般叶雹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上换吧,一...
    開(kāi)封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天折晦,我揣著相機(jī)與錄音,去河邊找鬼沾瓦。 笑死满着,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贯莺。 我是一名探鬼主播风喇,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缕探!你這毒婦竟也來(lái)了魂莫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤爹耗,失蹤者是張志新(化名)和其女友劉穎耙考,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體潭兽,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倦始,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了山卦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞋邑。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炫狱,到底是詐尸還是另有隱情藻懒,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布视译,位于F島的核電站,受9級(jí)特大地震影響归敬,放射性物質(zhì)發(fā)生泄漏酷含。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一汪茧、第九天 我趴在偏房一處隱蔽的房頂上張望椅亚。 院中可真熱鬧,春花似錦舱污、人聲如沸呀舔。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)媚赖。三九已至,卻和暖如春珠插,著一層夾襖步出監(jiān)牢的瞬間惧磺,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工捻撑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磨隘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓顾患,卻偏偏與公主長(zhǎng)得像番捂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子江解,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355