摘要:跳轉(zhuǎn)完了找到了被攔截了找不到了攔截器在模塊的時候講述的使用,如果本次路由跳轉(zhuǎn)不是走的綠色通道那么則會觸發(fā)攔截器進(jìn)行過濾。部分代碼省略攔截器的初始化在剛開始初始化的時候,就已經(jīng)做了這個操作。
目錄介紹
01.原生跳轉(zhuǎn)實現(xiàn)
02.實現(xiàn)組件跳轉(zhuǎn)方式
2.1 傳統(tǒng)跳轉(zhuǎn)方式
2.2 為何需要路由
03.ARouter配置與優(yōu)勢
04.跨進(jìn)程組件通信
4.1 URLScheme
4.2 AIDL
4.3 BroadcastReceiver
4.4 路由通信注意要點
05.ARouter的結(jié)構(gòu)
06.ARouter的工作流程
6.1 初始化流程
6.2 跳轉(zhuǎn)頁面流程
07.ARouter簡單調(diào)用api
7.1 最簡單調(diào)用
7.2 build源碼分析
7.3 navigation分析
08.Postcard信息攜帶
09.LogisticsCenter
10.DegradeService降級容錯服務(wù)
11.Interceptor攔截器
12.數(shù)據(jù)傳輸和自動注入
13.多dex的支持
14.InstantRun支持
15.生成的編譯代碼
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續(xù)搬到網(wǎng)上],轉(zhuǎn)載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議或者問題,萬事起于忽微,量變引起質(zhì)變!
注解學(xué)習(xí)小案例注解學(xué)習(xí)小案例,比較系統(tǒng)性學(xué)習(xí)注解并且應(yīng)用實踐。簡單應(yīng)用了運行期注解,通過注解實現(xiàn)了setContentView功能;簡單應(yīng)用了編譯器注解,通過注解實現(xiàn)了防暴力點擊的功能,同時支持設(shè)置時間間隔;使用注解替代枚舉;使用注解一步步搭建簡單路由案例。結(jié)合相應(yīng)的博客,在來一些小案例,從此應(yīng)該對注解有更加深入的理解……
開源項目地址:https://github.com/yangchong2...
01.原生跳轉(zhuǎn)實現(xiàn)Google提供的原聲路由主要是通過intent,可以分成顯示和隱式兩種。顯示的方案會導(dǎo)致類之間的直接依賴問題,耦合嚴(yán)重;隱式intent需要的配置清單中統(tǒng)一聲明,首先有個暴露的問題,另外在多模塊開發(fā)中協(xié)作也比較困難。只要調(diào)用startActivity后面的環(huán)節(jié)我們就無法控制了,在出現(xiàn)錯誤時無能為力。
02.實現(xiàn)組件跳轉(zhuǎn)方式 2.1 傳統(tǒng)跳轉(zhuǎn)方式第一種,通過intent跳轉(zhuǎn)
第二種,通過aidl跳轉(zhuǎn)
第三種,通過scheme協(xié)議跳轉(zhuǎn)
2.2 為何需要路由顯示Intent:項目龐大以后,類依賴耦合太大,不適合組件化拆分
隱式Intent:協(xié)作困難,調(diào)用時候不知道調(diào)什么參數(shù)
每個注冊了Scheme的Activity都可以直接打開,有安全風(fēng)險
AndroidMainfest集中式管理比較臃腫
無法動態(tài)修改路由,如果頁面出錯,無法動態(tài)降級
無法動態(tài)攔截跳轉(zhuǎn),譬如未登錄的情況下,打開登錄頁面,登錄成功后接著打開剛才想打開的頁面
H5、Android、iOS地址不一樣,不利于統(tǒng)一跳轉(zhuǎn)
03.ARouter配置與優(yōu)勢 3.1 ARouter的優(yōu)勢
如下所示
直接解析URL路由,解析參數(shù)并賦值
支持多模塊項目
支持InstantRun
允許自定義攔截器
ARouter可以提供IoC容器
映射關(guān)系自動注冊
靈活的降級策略
3.2 至于配置和使用直接看https://www.jianshu.com/p/fed...
04.跨進(jìn)程組件通信 4.1 URLScheme【例如:ActivityRouter、ARouter等】
優(yōu)勢有:
基因中自帶支持從webview中調(diào)用
不用互相注冊(不用知道需要調(diào)用的app的進(jìn)程名稱等信息)
劣勢有:
只能單向地給組件發(fā)送信息,適用于啟動Activity和發(fā)送指令,不適用于獲取數(shù)據(jù)(例如:獲取用戶組件的當(dāng)前用戶登錄信息)
需要有個額外的中轉(zhuǎn)Activity來統(tǒng)一處理URLScheme
如果設(shè)備上安裝了多個使用相同URLScheme的app,會彈出選擇框(多個組件作為app同時安裝到設(shè)備上時會出現(xiàn)這個問題)
無法進(jìn)行權(quán)限設(shè)置,無法進(jìn)行開關(guān)設(shè)置,存在安全性風(fēng)險
4.2 AIDL
優(yōu)勢有:
可以傳遞Parcelable類型的對象
效率高
可以設(shè)置跨app調(diào)用的開關(guān)
劣勢有:
調(diào)用組件之前需要提前知道該組件在那個進(jìn)程,否則無法建立ServiceConnection
組件在作為獨立app和作為lib打包到主app時,進(jìn)程名稱不同,維護(hù)成本高
4.3 BroadcastReceiverBroadcastReceiver + Service + LocalSocket。該方案是參考cc路由框架!
跨組件間通信實現(xiàn)的同時,應(yīng)該滿足以下條件:
每個app都能給其它app調(diào)用
app可以設(shè)置是否對外提供跨進(jìn)程組件調(diào)用的支持
組件調(diào)用的請求發(fā)出去之后,能自動探測當(dāng)前設(shè)備上是否有支持此次調(diào)用的app
支持超時、取消
4.4 路由通信注意要點 05.ARouter的結(jié)構(gòu)
ARouter主要由三部分組成,包括對外提供的api調(diào)用模塊、注解模塊以及編譯時通過注解生產(chǎn)相關(guān)的類模塊。
arouter-annotation注解的聲明和信息存儲類的模塊
arouter-compiler編譯期解析注解信息并生成相應(yīng)類以便進(jìn)行注入的模塊
arouter-api核心調(diào)用Api功能的模塊
annotation模塊
Route、Interceptor、Autowired都是在開發(fā)是需要的注解。
compiler模塊
AutoWiredProcessor、InterceptorProcessor、RouteProcessor分別為annotation模塊對應(yīng)的Autowired、Interceptor、Route在項目編譯時產(chǎn)生相關(guān)的類文件。
api模塊
主要是ARouter具體實現(xiàn)和對外暴露使用的api。
06.ARouter的工作流程 6.1 初始化流程
初始化代碼如下所示
/**
*/ 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 { /**
*/ public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; executor = tpe; try { long startInit = System.currentTimeMillis(); SetrouterMap; //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 ())); } 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. //導(dǎo)入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 //導(dǎo)入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 //導(dǎo)入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() + "]"); } } } ```
綜上所述,整個初始化的流程大概就是:
初始化運行時的上下文環(huán)境
初始化日志logger
尋找router相關(guān)的類
解析并且緩存路由相關(guān)信息
初始化攔截服務(wù)
6.2 跳轉(zhuǎn)頁面流程 07.ARouter調(diào)用api 7.1 最簡單調(diào)用
最簡單的調(diào)用方式
ARouter.getInstance() .build("/user/UserFragment") .navigation();7.2 build源碼分析
這個主要是添加跳轉(zhuǎn)的路徑
public Postcard build(String path) { return _ARouter.getInstance().build(path); }
然后把這個路徑添加到默認(rèn)的組中
/**
*/ 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); } //是否為綠色通道,是否進(jìn)過攔截器處理 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) { //沒有上下文環(huán)境,就用Application的上下文環(huán)境 final Context currentContext = null == context ? mContext : context; switch (postcard.getType()) { case ACTIVITY: // Build intent 構(gòu)建跳轉(zhuǎn)的intent final Intent intent = new Intent(currentContext, postcard.getDestination()); intent.putExtras(postcard.getExtras()); // Set flags. 設(shè)置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主要為信息的攜帶者,內(nèi)容是在構(gòu)造一次路由信息的時候生產(chǎn)的,其繼承于RouteMeta。RouteMeta是在代碼編譯時生成的內(nèi)容,主要在初始化WareHouse時對跳轉(zhuǎn)信息做了緩存。
看看代碼如下所示
//Postcard繼承于RouteMeta public final class Postcard extends RouteMeta //然后看看編譯生成的文件 /**
public class ARouter$$Group$$me implements IRouteGroup { @Override public void loadInto(Map10.DegradeService降級容錯服務(wù)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)); } } ```
首先,自定義一個類,需要繼承DegradeService類,如下所示
/** ** @author 楊充 * blog : https://github.com/yangchong211 * time : 2018/08/24 * desc : ARouter路由降級處理 * revise:*/ @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"+"---跳轉(zhuǎn)完了"); } @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的使用,如果本次路由跳轉(zhuǎn)不是走的綠色通道那么則會觸發(fā)攔截器進(jìn)行過濾。
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() { /*** * @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; } } ```
攔截器的初始化
在剛開始初始化的時候,就已經(jīng)做了這個操作。
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)) { //循環(huán)遍歷倉庫中的攔截器 for (Map.Entry> entry : Warehouse.interceptorsIndex.entrySet()) { Class extends IInterceptor> interceptorClass = entry.getValue(); try { //反射機(jī)制構(gòu)造自定義的每一個攔截器實例 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) { //檢測是否初始化完所有的爛機(jī)器 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); } } //執(zhí)行攔截器的過濾事件 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(); //如果當(dāng)前沒有攔截過濾,那么使用下一個攔截器 _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.數(shù)據(jù)傳輸和自動注入 13.多dex的支持
可查看multidex源碼:
public class ClassUtils { /** * Identifies if the current VM has a native support for multidex, meaning there is no need for* * @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 命令的行為,可以大幅縮短應(yīng)用更新的時間。盡管首次構(gòu)建可能需要花費較長的時間,Instant Run 在向應(yīng)用推送后續(xù)更新時則無需構(gòu)建新的 APK,因此,這樣可以更快地看到更改。
15.生成的編譯代碼
如下所示
關(guān)于其他內(nèi)容介紹 01.關(guān)于博客匯總鏈接1.技術(shù)博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關(guān)于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
掘金:https://juejin.im/user/593943...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/73348.html
摘要:使用實現(xiàn)功能運行期注解案例使用簡單的注解,便可以設(shè)置布局,等效于使用實現(xiàn)路由綜合型案例比較全面的介紹從零起步,一步一步封裝簡易的路由開源庫。申明注解用的就是。返回值表示這個注解里可以存放什么類型值。 YCApt關(guān)于apt方案實踐與總結(jié) 目錄介紹 00.注解系列博客匯總 01.什么是apt 02.annotationProcessor和apt區(qū)別 03.項目目錄結(jié)構(gòu) 04.該案例作用 ...
摘要:原文地址前言起源組件化方案分析業(yè)務(wù)組件的劃分和代碼隔離路由框架基礎(chǔ)庫的優(yōu)勢簡介什么是組件化為什么要組件化分析現(xiàn)有的組件化方案如何選擇組件化方案組件化方案描述架構(gòu)圖一覽架構(gòu)圖詳解宿主層業(yè)務(wù)層業(yè)務(wù)模塊的拆分基礎(chǔ)層核心基礎(chǔ)業(yè)務(wù)公共服務(wù)基礎(chǔ)組件其他 原文地址: https://www.jianshu.com/p/f67... 0 前言 0.1 起源 0.2 組件化方案分析 0.2....
閱讀 2919·2021-11-24 09:39
閱讀 1176·2021-11-02 14:38
閱讀 4177·2021-09-10 11:26
閱讀 2764·2021-08-25 09:40
閱讀 2320·2019-08-30 15:54
閱讀 492·2019-08-30 10:56
閱讀 2758·2019-08-26 12:14
閱讀 3228·2019-08-26 12:13