安卓組件化應(yīng)用的6.0權(quán)限適配優(yōu)化方案

組件化應(yīng)用

組件化應(yīng)用的概念最近挺火的。隨著app版本的迭代,業(yè)務(wù)也會(huì)變的越來越復(fù)雜。組件化應(yīng)用能將每個(gè)業(yè)務(wù)都單獨(dú)分成一個(gè)模塊,作為一個(gè)組件(Module),業(yè)務(wù)模塊彼此互不依賴,然后讓這些業(yè)務(wù)模塊都依賴公共模塊(也是Module)等,用路由的方式替代startactivity進(jìn)行模塊間的跳轉(zhuǎn)和數(shù)據(jù)傳遞。這就是組件化應(yīng)用的簡(jiǎn)單概念。

u=3895375886,3606044301&fm=11&gp=0.jpg

因?yàn)閙odule與module之間是代碼隔離的,互不依賴,所以添加或移除module是很方便的,也方便了應(yīng)用的多人并行開發(fā)。

Android6.0權(quán)限

Android6.0已經(jīng)出來快兩年了,除了繼續(xù)推進(jìn)Material Design,相信最直觀的改變就是權(quán)限申請(qǐng)方式了:權(quán)限模式從一開始的全部列出授予,變成了現(xiàn)在的運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)。下圖列出了截止7月份最新的系統(tǒng)占有率。雖然碎片化問題依舊嚴(yán)重,但6.0以上系統(tǒng)占有率也已經(jīng)接近一半了。所以說各位安卓開發(fā)者們,如果你的應(yīng)用還沒適配6.0的話,可要抓緊了。

QQ截圖20170810160140.png

如何適配

簡(jiǎn)單介紹了上面的兩個(gè)概念后,咱們切回正題。因?yàn)樽罱谧鲆粋€(gè)APP的組件化改造,原來的權(quán)限適配方案是跳到一個(gè)空白activity做申請(qǐng)然后回調(diào)申請(qǐng)結(jié)果的,但是現(xiàn)在module與module之間是互不依賴的,所以activity之間接口回調(diào)的方式是行不通的。一時(shí)間不知道有什么好的方法,也參考了github上幾個(gè)主流的權(quán)限適配庫(kù),但遺憾都沒有對(duì)組件化應(yīng)用提供一個(gè)專門的解決方案。
因?yàn)檫@個(gè)項(xiàng)目采用的路由是阿里的ARouter。查閱文檔后發(fā)現(xiàn)里面有攔截器的功能,下面是簡(jiǎn)單的示例代碼。

// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過程中處理登陸事件,這樣就不需要在目標(biāo)頁(yè)重復(fù)做登陸檢查
// 攔截器會(huì)在跳轉(zhuǎn)之間執(zhí)行,多個(gè)攔截器會(huì)按優(yōu)先級(jí)順序依次執(zhí)行
  @Interceptor(priority = 8, name = "測(cè)試用攔截器")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
    
    callback.onContinue(postcard);  // 處理完成,交還控制權(quán)
    // callback.onInterrupt(new RuntimeException("我覺得有點(diǎn)異常"));      // 覺得有問題,中斷路由流程

    // 以上兩種至少需要調(diào)用其中一種,否則不會(huì)繼續(xù)路由
    }

    @Override
    public void init(Context context) {
    // 攔截器的初始化,會(huì)在sdk初始化的時(shí)候調(diào)用該方法,僅會(huì)調(diào)用一次
    }
}

那么我們何不利用這個(gè)攔截器,在跳到一個(gè)需要頁(yè)面(比如相機(jī)頁(yè)面)之前 進(jìn)行統(tǒng)一攔截,然后判斷權(quán)限是否擁有,如果后則callback.onContinue(postcard),繼續(xù)跳轉(zhuǎn),否則callback.onInterrupt(null),攔截跳轉(zhuǎn)并執(zhí)行權(quán)限申請(qǐng)。
同時(shí)通常一個(gè)應(yīng)用通常有必要權(quán)限,沒有必要權(quán)限應(yīng)用無法正常運(yùn)行,如Manifest.permission.READ_PHONE_STATE,還有非必要權(quán)限,如Manifest.permission.ACCESS_FINE_LOCATION。
那么我們可以用priority字段創(chuàng)建優(yōu)先級(jí)最高的攔截器,檢測(cè)所有跳轉(zhuǎn)時(shí)是否有必要權(quán)限,否則先申請(qǐng)必要權(quán)限,然后繼續(xù)觸發(fā)下一個(gè)優(yōu)先級(jí)低的攔截器攔截檢測(cè)普通權(quán)限。

實(shí)際實(shí)現(xiàn)

定義一個(gè)permissionActivity,讓所有需要跳轉(zhuǎn)權(quán)限的頁(yè)面的activity繼承這個(gè),因?yàn)槿缟衔闹v的,我們實(shí)際權(quán)限的申請(qǐng)和處理不是在真正需要權(quán)限的頁(yè)面完成,而是在上一個(gè)頁(yè)面申請(qǐng)完成再跳到需要權(quán)限的頁(yè)面的。如果頁(yè)面太多建議所有頁(yè)面都繼承這個(gè)activity。

public class PermissionActivity extends FragmentActivity {
  private PermissionListener permissionListener;

    public void setPermissionListener(PermissionListener permissionListener) {
        this.permissionListener = permissionListener;
    }
    /**
     * 權(quán)限請(qǐng)求結(jié)果
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (permissionListener != null ) {
            permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

創(chuàng)建攔權(quán)限截器:

@Interceptor(priority = 1)
public class PermissionInterceptor implements IInterceptor {

    @Override
    public void process(final Postcard postcard, final InterceptorCallback callback) {
        final Activity activity = ActivityHelper.last();
        final  String[] PERMISSIONS = new String[]{
            Manifest.permission.READ_EXTERNAL_STORAGE,
    };
        if (activity != null && !PermissionHelper.hasPermission(activity)) {
            permissionRequest(postcard, callback, (PermissionActivity) activity, permissions);
        } else {
            callback.onContinue(postcard);  // 已有權(quán)限,無需申請(qǐng),繼續(xù)跳轉(zhuǎn)
        }
    }

    private void permissionRequest(final Postcard postcard, final InterceptorCallback interceptorCallback, final PermissionActivity activity, final String[] permissions) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                PermissionHelper.requestPermissions(activity, permissions, new PermissionListener() {
                    @Override
                    public void onsuccessed() {
                        callback.onContinue(postcard);// 權(quán)限申請(qǐng)成功,繼續(xù)跳轉(zhuǎn)
                    }

                    @Override
                    public boolean onfail() {
                        callback.onInterrupt(null);// 權(quán)限申請(qǐng)失敗,攔截跳轉(zhuǎn)
                    }
                });
            }
        };
        MainLooper.runOnUiThread(runnable );
    }

    @Override
    public void init(Context context) {
    
    }
}

實(shí)際實(shí)現(xiàn)中有幾個(gè)注意點(diǎn):
1.攔截器init(Context context)方法中的context不是當(dāng)前頁(yè)的activity,而是application,也就是說你在攔截器里是無法直接拿到當(dāng)前頁(yè)的上下文的。所以你需要額外維護(hù)一個(gè)activity棧,在每個(gè)acitivity的oncreate()方法中入棧,在ondestory()中出棧,然后在攔截器里面取出棧頂?shù)腶citivity,也就是當(dāng)前頁(yè)的acitivity。
2.void process(Postcard postcard, InterceptorCallback callback)方法在子線程中執(zhí)行,如果要執(zhí)行權(quán)限申請(qǐng)需要先切換回主線程。

public class MainLooper extends Handler {
    private static MainLooper instance = new MainLooper(Looper.getMainLooper());

    protected MainLooper(Looper looper) {
        super(looper);
    }

    public static MainLooper getInstance() {
        return instance;
    }

    public static void runOnUiThread(Runnable runnable) {
        if(Looper.getMainLooper().equals(Looper.myLooper())) {
            runnable.run();
        } else {
            instance.post(runnable);
        }

    }
}

總結(jié)

這樣實(shí)現(xiàn)的好處也是顯而易見的:所有的權(quán)限申請(qǐng)操作都在攔截器里完成,對(duì)原代碼無入侵:所有的權(quán)限申請(qǐng)彈框都出現(xiàn)在需要權(quán)限的頁(yè)面上一個(gè)activity。我們只要讓這個(gè)activity繼承permissionActivity并處理onRequestPermissionsResult的回調(diào)即可,其他譬如處理權(quán)限申請(qǐng),處理權(quán)限申請(qǐng)成功,失敗操作都統(tǒng)一在攔截器里完成即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容