目錄介紹
- 01.原生跳轉實現
- 02.實現元件跳轉方式
- 2.1 傳統跳轉方式
- 2.2 為何需要路由
- 03.ARouter配置與優勢
- 04.跨程式元件通訊
- 4.1 URLScheme
- 4.2 AIDL
- 4.3 BroadcastReceiver
- 4.4 路由通訊注意要點
- 05.ARouter的結構
- 06.ARouter的工作流程
- 6.1 初始化流程
- 6.2 跳轉頁面流程
- 07.ARouter簡單呼叫api
- 7.1 最簡單呼叫
- 7.2 build原始碼分析
- 7.3 navigation分析
- 08.Postcard資訊攜帶
- 09.LogisticsCenter
- 10.DegradeService降級容錯服務
- 11.Interceptor攔截器
- 12.資料傳輸和自動注入
- 13.多dex的支援
- 14.InstantRun支援
- 15.生成的編譯程式碼
好訊息
- 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
- 連結地址:github.com/yangchong21…
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議或者問題,萬事起於忽微,量變引起質變!
註解學習小案例
- 註解學習小案例,比較系統性學習註解並且應用實踐。簡單應用了執行期註解,通過註解實現了setContentView功能;簡單應用了編譯器註解,通過註解實現了防暴力點選的功能,同時支援設定時間間隔;使用註解替代列舉;使用註解一步步搭建簡單路由案例。結合相應的部落格,在來一些小案例,從此應該對註解有更加深入的理解……
- 開源專案地址:github.com/yangchong21…
01.原生跳轉實現
- Google提供的原聲路由主要是通過intent,可以分成顯示和隱式兩種。顯示的方案會導致類之間的直接依賴問題,耦合嚴重;隱式intent需要的配置清單中統一宣告,首先有個暴露的問題,另外在多模組開發中協作也比較困難。只要呼叫startActivity後面的環節我們就無法控制了,在出現錯誤時無能為力。
02.實現元件跳轉方式
2.1 傳統跳轉方式
- 第一種,通過intent跳轉
- 第二種,通過aidl跳轉
- 第三種,通過scheme協議跳轉
2.2 為何需要路由
- 顯示Intent:專案龐大以後,類依賴耦合太大,不適合元件化拆分
- 隱式Intent:協作困難,呼叫時候不知道調什麼引數
- 每個註冊了Scheme的Activity都可以直接開啟,有安全風險
- AndroidMainfest集中式管理比較臃腫
- 無法動態修改路由,如果頁面出錯,無法動態降級
- 無法動態攔截跳轉,譬如未登入的情況下,開啟登入頁面,登入成功後接著開啟剛才想開啟的頁面
- H5、Android、iOS地址不一樣,不利於統一跳轉
03.ARouter配置與優勢
3.1 ARouter的優勢
- 如下所示
- 直接解析URL路由,解析引數並賦值
- 支援多模組專案
- 支援InstantRun
- 允許自定義攔截器
- ARouter可以提供IoC容器
- 對映關係自動註冊
- 靈活的降級策略
3.2 至於配置和使用
- 直接看https://www.jianshu.com/p/fed5d5b95bae
04.跨程式元件通訊
4.1 URLScheme【例如:ActivityRouter、ARouter等】
- 優勢有:
- 基因中自帶支援從webview中呼叫
- 不用互相註冊(不用知道需要呼叫的app的程式名稱等資訊)
- 劣勢有:
- 只能單向地給元件傳送資訊,適用於啟動Activity和傳送指令,不適用於獲取資料(例如:獲取使用者元件的當前使用者登入資訊)
- 需要有個額外的中轉Activity來統一處理URLScheme
- 如果裝置上安裝了多個使用相同URLScheme的app,會彈出選擇框(多個元件作為app同時安裝到裝置上時會出現這個問題)
- 無法進行許可權設定,無法進行開關設定,存在安全性風險
4.2 AIDL
- 優勢有:
- 可以傳遞Parcelable型別的物件
- 效率高
- 可以設定跨app呼叫的開關
- 劣勢有:
- 呼叫元件之前需要提前知道該元件在那個程式,否則無法建立ServiceConnection
- 元件在作為獨立app和作為lib打包到主app時,程式名稱不同,維護成本高
4.3 BroadcastReceiver
- BroadcastReceiver + Service + LocalSocket。該方案是參考cc路由框架!
- 跨元件間通訊實現的同時,應該滿足以下條件:
- 每個app都能給其它app呼叫
- app可以設定是否對外提供跨程式元件呼叫的支援
- 元件呼叫的請求發出去之後,能自動探測當前裝置上是否有支援此次呼叫的app
- 支援超時、取消
4.4 路由通訊注意要點
05.ARouter的結構
- ARouter主要由三部分組成,包括對外提供的api呼叫模組、註解模組以及編譯時通過註解生產相關的類模組。
- arouter-annotation註解的宣告和資訊儲存類的模組
- arouter-compiler編譯期解析註解資訊並生成相應類以便進行注入的模組
- arouter-api核心呼叫Api功能的模組
- annotation模組
- Route、Interceptor、Autowired都是在開發是需要的註解。
- compiler模組
- AutoWiredProcessor、InterceptorProcessor、RouteProcessor分別為annotation模組對應的Autowired、Interceptor、Route在專案編譯時產生相關的類檔案。
- api模組
- 主要是ARouter具體實現和對外暴露使用的api。
06.ARouter的工作流程
6.1 初始化流程
- 初始化程式碼如下所示
/** * Init, it must be call before used router. */ public static void init(Application application) { //如果沒有初始化,則 if (!hasInit) { logger = _ARouter.logger; _ARouter.logger.info(Consts.TAG, "ARouter init start."); //做初始化工作 hasInit = _ARouter.init(application); if (hasInit) { _ARouter.afterInit(); } _ARouter.logger.info(Consts.TAG, "ARouter init over."); } } 複製程式碼
- 之後接著看_ARouter.init(application)這行程式碼,點選去檢視
protected static synchronized boolean init(Application application) { //賦值上下文 mContext = application; //初始化LogisticsCenter LogisticsCenter.init(mContext, executor); logger.info(Consts.TAG, "ARouter init success!"); hasInit = true; mHandler = new Handler(Looper.getMainLooper()); return true; } 複製程式碼
- 接下來看看LogisticsCenter裡面做了什麼
public class LogisticsCenter { /** * LogisticsCenter init, load all metas in memory. Demand initialization */ public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; executor = tpe; try { long startInit = System.currentTimeMillis(); Set<String> routerMap; //debug或者版本更新的時候每次都重新載入router資訊 // It will rebuild router map every times when debuggable. if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) { logger.info(TAG, "Run with debug mode or new install, rebuild router map."); // These class was generate by arouter-compiler. //載入alibaba.android.arouter.routes包下載的類 routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); if (!routerMap.isEmpty()) { context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); } PackageUtils.updateVersion(context); // Save new version name when router map update finish. } else { logger.info(TAG, "Load router map from cache."); routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>())); } logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms."); startInit = System.currentTimeMillis(); for (String className : routerMap) { if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { // This one of root elements, load root. //匯入ARouter$$Root$$app.java,初始化Warehouse.groupsIndex集合 ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { // Load interceptorMeta //匯入ARouter$$Interceptors$$app.java,初始化Warehouse.interceptorsIndex集合 ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { // Load providerIndex //匯入ARouter$$Providers$$app.java,初始化Warehouse.providersIndex集合 ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } /*******部分程式碼省略********/ } catch (Exception e) { throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]"); } } } 複製程式碼
- 綜上所述,整個初始化的流程大概就是:
- 初始化執行時的上下文環境
- 初始化日誌logger
- 尋找router相關的類
- 解析並且快取路由相關資訊
- 初始化攔截服務
6.2 跳轉頁面流程
07.ARouter呼叫api
7.1 最簡單呼叫
- 最簡單的呼叫方式
ARouter.getInstance() .build("/user/UserFragment") .navigation(); 複製程式碼
7.2 build原始碼分析
- 這個主要是新增跳轉的路徑
public Postcard build(String path) { return _ARouter.getInstance().build(path); } 複製程式碼
- 然後把這個路徑新增到預設的組中
/** * Build postcard by path and default group */ protected Postcard build(String path) { if (TextUtils.isEmpty(path)) { throw new HandlerException(Consts.TAG + "Parameter is invalid!"); } else { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { path = pService.forString(path); } return build(path, extractGroup(path)); } } 複製程式碼
7.3 navigation分析
- 如下所示
final class _ARouter { protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { try { LogisticsCenter.completion(postcard); } catch (NoRouteFoundException ex) { /**************部分程式碼省略***************/ if (null != callback) { callback.onLost(postcard); } else { // No callback for this invoke, then we use the global degrade service. DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class); if (null != degradeService) { degradeService.onLost(context, postcard); } } return null; } if (null != callback) { callback.onFound(postcard); } //是否為綠色通道,是否進過攔截器處理 if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR. interceptorService.doInterceptions(postcard, new InterceptorCallback() { @Override public void onContinue(Postcard postcard) { _navigation(context, postcard, requestCode, callback); } @Override public void onInterrupt(Throwable exception) { //中斷處理 if (null != callback) { callback.onInterrupt(postcard); } } }); } else { return _navigation(context, postcard, requestCode, callback); } return null; } private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { //沒有上下文環境,就用Application的上下文環境 final Context currentContext = null == context ? mContext : context; switch (postcard.getType()) { case ACTIVITY: // Build intent 構建跳轉的intent final Intent intent = new Intent(currentContext, postcard.getDestination()); intent.putExtras(postcard.getExtras()); // Set flags. 設定flag int flags = postcard.getFlags(); if (-1 != flags) { intent.setFlags(flags); } else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag. //如果上下文不是Activity,則新增FLAG_ACTIVITY_NEW_TASK的flag intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } // Navigation in main looper. 切換到主執行緒中 new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if (requestCode > 0) { // Need start for result ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle()); } else { ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle()); } if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version. ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim()); } if (null != callback) { // Navigation over. callback.onArrival(postcard); } } }); break; case PROVIDER: return postcard.getProvider(); case BOARDCAST: case CONTENT_PROVIDER: case FRAGMENT: Class fragmentMeta = postcard.getDestination(); try { Object instance = fragmentMeta.getConstructor().newInstance(); if (instance instanceof Fragment) { ((Fragment) instance).setArguments(postcard.getExtras()); } else if (instance instanceof android.support.v4.app.Fragment) { ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras()); } return instance; } catch (Exception ex) { logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace())); } case METHOD: case SERVICE: default: return null; } return null; } } 複製程式碼
08.Postcard資訊攜帶
- Postcard主要為資訊的攜帶者,內容是在構造一次路由資訊的時候生產的,其繼承於RouteMeta。RouteMeta是在程式碼編譯時生成的內容,主要在初始化WareHouse時對跳轉資訊做了快取。
- 看看程式碼如下所示
//Postcard繼承於RouteMeta public final class Postcard extends RouteMeta //然後看看編譯生成的檔案 /** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */ public class ARouter$$Group$$me implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/me/ExperienceCouponActivity", RouteMeta.build(RouteType.ACTIVITY, ExperienceCouponActivity.class, "/me/experiencecouponactivity", "me", null, -1, -2147483648)); atlas.put("/me/ServiceActivity", RouteMeta.build(RouteType.ACTIVITY, ServiceActivity.class, "/me/serviceactivity", "me", null, -1, -2147483648)); atlas.put("/me/SettingActivity", RouteMeta.build(RouteType.ACTIVITY, SettingActivity.class, "/me/settingactivity", "me", null, -1, -2147483648)); atlas.put("/me/UdeskServiceActivity", RouteMeta.build(RouteType.ACTIVITY, UdeskServiceActivity.class, "/me/udeskserviceactivity", "me", null, -1, -2147483648)); } } 複製程式碼
10.DegradeService降級容錯服務
- 首先,自定義一個類,需要繼承DegradeService類,如下所示
/** * <pre> * @author 楊充 * blog : https://github.com/yangchong211 * time : 2018/08/24 * desc : ARouter路由降級處理 * revise: * </pre> */ @Route(path = DegradeServiceImpl.PATH) public class DegradeServiceImpl implements DegradeService { static final String PATH = "/service/DegradeServiceImpl"; @Override public void onLost(Context context, Postcard postcard) { if (context != null && postcard.getGroup().equals("activity")) { Intent intent = new Intent(context, WebViewActivity.class); intent.putExtra(Constant.URL, Constant.GITHUB); intent.putExtra(Constant.TITLE, "github地址"); ActivityCompat.startActivity(context, intent, null); } } @Override public void init(Context context) { } } 複製程式碼
- 如何使用該降級方案,十分簡單。
NavigationCallback callback = new NavCallback() { @Override public void onArrival(Postcard postcard) { LogUtils.i("ARouterUtils"+"---跳轉完了"); } @Override public void onFound(Postcard postcard) { super.onFound(postcard); LogUtils.i("ARouterUtils"+"---找到了"); } @Override public void onInterrupt(Postcard postcard) { super.onInterrupt(postcard); LogUtils.i("ARouterUtils"+"---被攔截了"); } @Override public void onLost(Postcard postcard) { super.onLost(postcard); LogUtils.i("ARouterUtils"+"---找不到了"); DegradeServiceImpl degradeService = new DegradeServiceImpl(); degradeService.onLost(Utils.getApp(),postcard); } }; 複製程式碼
11.Interceptor攔截器
- 在ARouter模組的時候講述Interceptor的使用,如果本次路由跳轉不是走的綠色通道那麼則會觸發攔截器進行過濾。
final class _ARouter { protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { /************部分程式碼省略************/ if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR. interceptorService.doInterceptions(postcard, new InterceptorCallback() { /** * Continue process * * @param postcard route meta */ @Override public void onContinue(Postcard postcard) { _navigation(context, postcard, requestCode, callback); } /** * Interrupt process, pipeline will be destory when this method called. * * @param exception Reson of interrupt. */ @Override public void onInterrupt(Throwable exception) { if (null != callback) { callback.onInterrupt(postcard); } logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage()); } }); } else { return _navigation(context, postcard, requestCode, callback); } return null; } } 複製程式碼
- 攔截器的初始化
- 在剛開始初始化的時候,就已經做了這個操作。
final class _ARouter { static void afterInit() { // Trigger interceptor init, use byName. interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation(); } } 複製程式碼
- InterceptorServiceImpl的init方法:
@Route(path = "/arouter/service/interceptor") public class InterceptorServiceImpl implements InterceptorService { @Override public void init(final Context context) { LogisticsCenter.executor.execute(new Runnable() { @Override public void run() { if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) { //迴圈遍歷倉庫中的攔截器 for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) { Class<? extends IInterceptor> interceptorClass = entry.getValue(); try { //反射機制構造自定義的每一個攔截器例項 IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance(); iInterceptor.init(context); //並將其新增在快取中 Warehouse.interceptors.add(iInterceptor); } catch (Exception ex) { throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]"); } } interceptorHasInit = true; logger.info(TAG, "ARouter interceptors init over."); synchronized (interceptorInitLock) { interceptorInitLock.notifyAll(); } } } }); } } 複製程式碼
- 攔截器的工作過程
@Route(path = "/arouter/service/interceptor") public class InterceptorServiceImpl implements InterceptorService { @Override public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) { if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) { //檢測是否初始化完所有的爛機器 checkInterceptorsInitStatus(); //沒有完成正常的初始化,拋異常 if (!interceptorHasInit) { callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time.")); return; } //順序遍歷每一個攔截器, LogisticsCenter.executor.execute(new Runnable() { @Override public void run() { CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size()); try { _excute(0, interceptorCounter, postcard); interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS); //攔截器的遍歷終止之後,如果有還有沒有遍歷的攔截器,則表示路由事件被攔截 if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn't return anythings. callback.onInterrupt(new HandlerException("The interceptor processing timed out.")); } else if (null != postcard.getTag()) { // Maybe some exception in the tag. callback.onInterrupt(new HandlerException(postcard.getTag().toString())); } else { callback.onContinue(postcard); } } catch (Exception e) { callback.onInterrupt(e); } } }); } else { callback.onContinue(postcard); } } //執行攔截器的過濾事件 private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) { if (index < Warehouse.interceptors.size()) { IInterceptor iInterceptor = Warehouse.interceptors.get(index); iInterceptor.process(postcard, new InterceptorCallback() { @Override public void onContinue(Postcard postcard) { // Last interceptor excute over with no exception. counter.countDown(); //如果當前沒有攔截過濾,那麼使用下一個攔截器 _excute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know. } @Override public void onInterrupt(Throwable exception) { // Last interceptor excute over with fatal exception. postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage()); // save the exception message for backup. counter.cancel(); } }); } } } 複製程式碼
12.資料傳輸和自動注入
13.多dex的支援
- 可檢視multidex原始碼:
public class ClassUtils { /** * Identifies if the current VM has a native support for multidex, meaning there is no need for * additional installation by this library. * * @return true if the VM handles multidex */ private static boolean isVMMultidexCapable() { boolean isMultidexCapable = false; String vmName = null; try { if (isYunOS()) { // YunOS需要特殊判斷 vmName = "'YunOS'"; isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21; } else { // 非YunOS原生Android vmName = "'Android'"; String versionString = System.getProperty("java.vm.version"); if (versionString != null) { Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); if (matcher.matches()) { try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR) || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR) && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR)); } catch (NumberFormatException ignore) { // let isMultidexCapable be false } } } } } catch (Exception ignore) { } Log.i(Consts.TAG, "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support")); return isMultidexCapable; } } 複製程式碼
14.InstantRun支援
- 什麼是InstantRun支援?
- Android Studio 2.0 中引入的 Instant Run 是 Run 和 Debug 命令的行為,可以大幅縮短應用更新的時間。儘管首次構建可能需要花費較長的時間,Instant Run 在嚮應用推送後續更新時則無需構建新的 APK,因此,這樣可以更快地看到更改。
15.生成的編譯程式碼
- 如下所示
關於其他內容介紹
01.關於部落格彙總連結
02.關於我的部落格
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:github.com/yangchong21…
- 知乎:www.zhihu.com/people/yczb…
- 簡書:www.jianshu.com/u/b7b2c6ed9…
- csdn:my.csdn.net/m0_37700275
- 喜馬拉雅聽書:www.ximalaya.com/zhubo/71989…
- 開源中國:my.oschina.net/zbj1618/blo…
- 泡在網上的日子:www.jcodecraeer.com/member/cont…
- 郵箱:yangchong211@163.com
- 阿里雲部落格:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:segmentfault.com/u/xiangjian…
- 掘金:juejin.im/user/593943…