組件化應用
組件化應用的概念最近挺火的肩狂。隨著app版本的迭代存捺,業(yè)務也會變的越來越復雜抖甘。組件化應用能將每個業(yè)務都單獨分成一個模塊,作為一個組件(Module)闷尿,業(yè)務模塊彼此互不依賴塑径,然后讓這些業(yè)務模塊都依賴公共模塊(也是Module)等,用路由的方式替代startactivity
進行模塊間的跳轉(zhuǎn)和數(shù)據(jù)傳遞填具。這就是組件化應用的簡單概念统舀。
因為module與module之間是代碼隔離的,互不依賴劳景,所以添加或移除module是很方便的誉简,也方便了應用的多人并行開發(fā)。
Android6.0權限
Android6.0已經(jīng)出來快兩年了盟广,除了繼續(xù)推進Material Design闷串,相信最直觀的改變就是權限申請方式了:權限模式從一開始的全部列出授予,變成了現(xiàn)在的運行時動態(tài)申請筋量。下圖列出了截止7月份最新的系統(tǒng)占有率烹吵。雖然碎片化問題依舊嚴重,但6.0以上系統(tǒng)占有率也已經(jīng)接近一半了桨武。所以說各位安卓開發(fā)者們肋拔,如果你的應用還沒適配6.0的話,可要抓緊了呀酸。
如何適配
簡單介紹了上面的兩個概念后凉蜂,咱們切回正題。因為最近在做一個APP的組件化改造性誉,原來的權限適配方案是跳到一個空白activity做申請然后回調(diào)申請結(jié)果的窿吩,但是現(xiàn)在module與module之間是互不依賴的,所以activity之間接口回調(diào)的方式是行不通的艾栋。一時間不知道有什么好的方法爆存,也參考了github上幾個主流的權限適配庫,但遺憾都沒有對組件化應用提供一個專門的解決方案蝗砾。
因為這個項目采用的路由是阿里的ARouter先较。查閱文檔后發(fā)現(xiàn)里面有攔截器的功能携冤,下面是簡單的示例代碼。
// 比較經(jīng)典的應用就是在跳轉(zhuǎn)過程中處理登陸事件闲勺,這樣就不需要在目標頁重復做登陸檢查
// 攔截器會在跳轉(zhuǎn)之間執(zhí)行曾棕,多個攔截器會按優(yōu)先級順序依次執(zhí)行
@Interceptor(priority = 8, name = "測試用攔截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
callback.onContinue(postcard); // 處理完成,交還控制權
// callback.onInterrupt(new RuntimeException("我覺得有點異常")); // 覺得有問題菜循,中斷路由流程
// 以上兩種至少需要調(diào)用其中一種翘地,否則不會繼續(xù)路由
}
@Override
public void init(Context context) {
// 攔截器的初始化,會在sdk初始化的時候調(diào)用該方法癌幕,僅會調(diào)用一次
}
}
那么我們何不利用這個攔截器衙耕,在跳到一個需要頁面(比如相機頁面)之前 進行統(tǒng)一攔截,然后判斷權限是否擁有勺远,如果后則callback.onContinue(postcard)
,繼續(xù)跳轉(zhuǎn)橙喘,否則callback.onInterrupt(null)
,攔截跳轉(zhuǎn)并執(zhí)行權限申請。
同時通常一個應用通常有必要權限,沒有必要權限應用無法正常運行胶逢,如Manifest.permission.READ_PHONE_STATE
,還有非必要權限厅瞎,如Manifest.permission.ACCESS_FINE_LOCATION
。
那么我們可以用priority
字段創(chuàng)建優(yōu)先級最高的攔截器初坠,檢測所有跳轉(zhuǎn)時是否有必要權限和簸,否則先申請必要權限,然后繼續(xù)觸發(fā)下一個優(yōu)先級低的攔截器攔截檢測普通權限碟刺。
實際實現(xiàn)
定義一個permissionActivity,讓所有需要跳轉(zhuǎn)權限的頁面的activity繼承這個锁保,因為如上文講的,我們實際權限的申請和處理不是在真正需要權限的頁面完成南誊,而是在上一個頁面申請完成再跳到需要權限的頁面的身诺。如果頁面太多建議所有頁面都繼承這個activity。
public class PermissionActivity extends FragmentActivity {
private PermissionListener permissionListener;
public void setPermissionListener(PermissionListener permissionListener) {
this.permissionListener = permissionListener;
}
/**
* 權限請求結(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)建攔權限截器:
@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); // 已有權限抄囚,無需申請霉赡,繼續(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);// 權限申請成功,繼續(xù)跳轉(zhuǎn)
}
@Override
public boolean onfail() {
callback.onInterrupt(null);// 權限申請失敗幔托,攔截跳轉(zhuǎn)
}
});
}
};
MainLooper.runOnUiThread(runnable );
}
@Override
public void init(Context context) {
}
}
實際實現(xiàn)中有幾個注意點:
1.攔截器init(Context context)
方法中的context
不是當前頁的activity穴亏,而是application,也就是說你在攔截器里是無法直接拿到當前頁的上下文的重挑。所以你需要額外維護一個activity棧嗓化,在每個acitivity的oncreate()
方法中入棧,在ondestory()
中出棧谬哀,然后在攔截器里面取出棧頂?shù)腶citivity刺覆,也就是當前頁的acitivity。
2.void process(Postcard postcard, InterceptorCallback callback)
方法在子線程中執(zhí)行史煎,如果要執(zhí)行權限申請需要先切換回主線程谦屑。
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é)
這樣實現(xiàn)的好處也是顯而易見的:所有的權限申請操作都在攔截器里完成驳糯,對原代碼無入侵:所有的權限申請彈框都出現(xiàn)在需要權限的頁面上一個activity。我們只要讓這個activity繼承permissionActivity并處理onRequestPermissionsResult
的回調(diào)即可氢橙,其他譬如處理權限申請酝枢,處理權限申請成功,失敗操作都統(tǒng)一在攔截器里完成即可悍手。