背景介紹:
張明慶老哥之前在得到工作時,開源了DDAndroidComponent項目,演示組件化思路及實現(xiàn),本人當時作為協(xié)作者參與了一部分開發(fā),現(xiàn)項目遷移到JIMU ,接下來將在新項目倉庫進行維護。
張明慶老哥的幾篇文章:
1、Android徹底組件化方案實踐
2、Android徹底組件化demo發(fā)布
3、Android徹底組件化-代碼和資源隔離
4、Android徹底組件化—UI跳轉升級改造
5、Android徹底組件化—如何使用Arouter
為什么要有這一篇
首先,不在此處展開組件化核心思想“隔離與發(fā)現(xiàn)”,因為有了隔離的要求(其他模塊的Activity在編譯期不可見,無法創(chuàng)建顯示Intent),和JIMU本身自有一套路由(我們稱之為“路由”,“IOC容器”,“DI組件”都是可以的,本質就是用來注冊和尋找實例的)的實際基礎存在,這決定了在組件化中跨模塊跳轉頁面,需要使用以下的一種技術:
- 將模塊內的頁面跳轉,整理出API,在ComponentService中暴露。
- 抽象頁面跳轉的過程,使用映射關系,由路由短鏈發(fā)起跳轉請求。
其次,為什么Demo中沒有直接使用ARouter等成型方案?ARouter是個很優(yōu)秀的項目,但是對于JIMU而言,他有點over-weight而且功能重復,我前面簡單提到:“JIMU本身自有一套路由”,而且在JIMU中自動注冊的功能是注冊到該路由的,再加入ARouter純粹多余(選擇多了往往是麻煩事),以及在項目中使用ARouter的,可以參考鏈接中的第五篇,使用ARouter進行路由跳轉,甚至是基于ARouter實現(xiàn)組件化,以達到項目的簡潔。
結合討論群中一些朋友們提出的問題,以及github上的典型issue,本文進行一些扼要的總結,便于大家排錯。
為了方便,我們下文將用UIRouter來代指JIMU中提供的UI路由。
以下是outline:
- UIRouter 1.0.0提供哪些東西
- UIRouter 1.0.0包含哪些已知問題
- 如何集成 UIRouter
- UIRouter的特性概述
- 常見問題Q/A
UIRouter 1.0.0提供哪些東西
- 依賴庫:router-annotation 包含了可用的注解以及內部使用的實體類、幫助類
- 注解處理器 router-anno-compiler
可供使用的注解:(代碼取自新版本,和v1.0.0存在一定小差異)
- RouteNode 路由節(jié)點,對應Activity
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface RouteNode {
/**
* path of one route
*/
String path();
/**
* The priority of route.
*
* we inspect the path and throw exception when duplicated
* paths were find, thus, it's useless and impossible to use priority
*/
@Deprecated
int priority() default -1;
/**
* description of the activity, user for gen route table
*/
String desc() default "";
}
- Autowired 用于從intent中獲取參數(shù),并將值注入Field的注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Autowired {
/**
* @return param's name or service name.
*/
String name() default "";
/**
* <em>primitive java type check will be ignore</em>
* check the result of DI, if inject failed, the value of
* the field will be null, if required, output log
*
* @return true for required,false otherwise
*/
boolean required() default false;
/**
* throw exception when the required field is null after inject.
* <p>
* It can help developer find most data delivering bugs when developing.
* but not suggest to open this function after release.
* <p>
* I suggest to define a Constant maintained manually
* <p>
* only activated when required = true and throwOnNull = true.
*
* @return true if throwing exception when null is required, false otherwise
*/
boolean throwOnNull() default false;
/**
* @return field description
*/
String desc() default "none desc.";
}
其他非暴露供使用的內容不做介紹。
UIRouter 1.0.0包含哪些已知問題
- 因單例模板出現(xiàn)問題導致:本設計成單例的JsonService和AutowireService不是單例
- 在一個Module中進行java和kotlin的混編并同時需要使用RouteNode注解時存在問題
如何集成 UIRouter
首先:您應該以及集成了JIMU方案,使用了gradle plugin 并且集成了基礎庫:
以下演示代碼均建立在java項目、gradle plugin版本<3.0 基礎上
- 集成注解依賴庫:
compile 'com.luojilab.ddcomponent:router-annotation:1.0.0'
注意:componentLib包中已經(jīng)包含了注解依賴庫,所以不需要再聲明依賴庫。
- 集成注解處理器:
annotationProcessor 'com.luojilab.ddcomponent:router-anno-compiler:1.0.0'
注意:在Module的build.gradle中聲明,不要在底層庫中聲明;不聲明無法使用UIRouter功能。
并指定Module的環(huán)境參數(shù):
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [host: "share"]
}
}
}
注意:這里的環(huán)境參數(shù)會影響使用時編寫的url(or URI),以此處代碼為例,我們的url形式是這樣的:
[schema]//[host][path]?[queryString]
舉個例子,該Module中存在一個RouteNode:path為“/index”
那么對應的url為:
【任意協(xié)議】//share/index
如果未指定,那么將使用默認值“default”,即:
【任意協(xié)議】//default/index
3.將生成的映射注冊到UIRouter
抱歉在之前的內容中,遺漏了這一塊,給使用者帶來了困擾
這一部分內容,在閱讀上而言放在后面會更好一點,但是容易被忽視。
在使用中,我們會得到自動生成的路由映射,一定要注冊到UIRouter;例如:
public class ShareApplike implements IApplicationLike {
UIRouter uiRouter = UIRouter.getInstance();
@Override
public void onCreate() {
uiRouter.registerUI("share");
}
@Override
public void onStop() {
uiRouter.unregisterUI("share");
}
}
我們選擇的是在組件的生命期入口進行注冊和反注冊。不多做贅述。
實際上這樣我們就完成了集成,接下來就是使用了。
為路由節(jié)點(Activity)添加注解
注意,priority沒有實質性意義,已廢棄,Module中不允許出現(xiàn)同樣的path
@RouteNode(path = "/main", desc = "首頁")
public class MainActivity extends BaseActivity implements View.OnClickListener {
//...代碼略去
}
以appModule中的MainActivity為例添加了一個節(jié)點。這樣我們可以得到一個生成類:AppUiRouter:
public class AppUiRouter extends BaseCompRouter {
@Override
public String getHost() {
return "app";
}
@Override
public void initMap() {
super.initMap();
routeMapper.put("/main",MainActivity.class);
}
}
一個用于輔助的Module路由表清單txt:AppRouterTable.txt
auto generated, do not change !!!!
HOST : app
首頁
/main
進行跳轉
UIRouter.getInstance().openUri({context},
"JIMU://app/main", {bundle});
關于參數(shù)
將在(二)中詳細展開,這里簡單介紹一下取參數(shù),取參數(shù)使用了Autowired注解,進行DI。
以一個新版中的演示Demo為例:
@RouteNode(path = "/uirouter/demo/2", desc = "使用bundle傳遞參數(shù)")
public class Demo2Activity extends TestActivity {
private static Bundle bundle = new Bundle();
static {
bundle.putString("foo", "foo string");
bundle.putString("EXTRA_STR_BAR", "bar string");
}
@Autowired() //不指定名稱時將使用變量名,若被混淆可能出現(xiàn)問題,
// 建議使用name指定key,參考bar的使用
String foo;
@Autowired(name = "EXTRA_STR_BAR")
String bar;
public static final UiRouterDemoActivity.Case aCase
= new UiRouterDemoActivity.Case(false,
"使用bundle傳遞參數(shù)",
"JIMU://app/uirouter/demo/2",
bundle);
@Override
protected void displayInfo(TextView textView) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("使用bundle傳遞參數(shù)成功\r\n");
stringBuilder.append("foo:").append(foo).append("\r\n");
stringBuilder.append("bar:").append(bar).append("\r\n");
textView.setText(stringBuilder.toString());
}
}
父類的代碼:
abstract class TestActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
textView = findViewById(R.id.demo_tv_info);
AutowiredService.Factory.getSingletonImpl()
.autowire(this);
displayInfo(textView);
}
protected abstract void displayInfo(TextView textView);
}
稍微細說一下:
需要被注入的Field為:
@Autowired() //不指定名稱時將使用變量名,若被混淆可能出現(xiàn)問題,
// 建議使用name指定key,參考bar的使用
String foo;
@Autowired(name = "EXTRA_STR_BAR")
String bar;
建議:指定name!
我們看看生成的內容中,多了哪些:
- 路由 AppUiRouter.java 中:
public class AppUiRouter extends BaseCompRouter {
@Override
public String getHost() {
return "app";
}
@Override
public void initMap() {
super.initMap();
routeMapper.put("/main",MainActivity.class);
routeMapper.put("/uirouter/demo/2",Demo2Activity.class);
paramsMapper.put(Demo2Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 8); put("EXTRA_STR_BAR", 8); }});
}
}
相比于MainActivity,多了一些東西:paramMapper中添加了一些配置,這里不詳細展開,在不hook系統(tǒng)API的情況下,都會回歸到使用Intent啟動Activity。而傳參依舊需要使用Bundle,我們知道bundle的讀寫是需要知道類型的。UIRouter支持的類型下一篇展開
- Demo2Activity$$Router$$Autowired.java文件
以下代碼僅演示下,不做展開,讀者不用深究,將在(二)中詳細展開
類似:
/**
* Auto generated by AutowiredProcessor */
public class Demo2Activity$$Router$$Autowired implements ISyringe {
private JsonService jsonService;
@Override
public void inject(Object target) {
jsonService = JsonService.Factory.getSingletonImpl();
Demo2Activity substitute = (Demo2Activity)target;
substitute.foo = substitute.getIntent().getStringExtra("foo");
substitute.bar = substitute.getIntent().getStringExtra("EXTRA_STR_BAR");
}
@Override
public void preCondition(Bundle bundle) throws ParamException {
}
}
- 路由表中多了以下內容:
使用bundle傳遞參數(shù)
/uirouter/demo/2
foo:String
EXTRA_STR_BAR:String
注意:多了參數(shù)信息,是 name的值 和 參數(shù)的類型
發(fā)起注入的核心代碼:和v1.0.0的API有一定區(qū)別
AutowiredService.Factory.getSingletonImpl().autowire(this);
UIRouter的特性概述
時間和篇幅原因,移到(二)中展開。
常見問題Q/A
- Q:為什么無法跳轉?
A:根本原因都是沒有正確集成:排查次序
- 是否集成了注解庫和注解處理庫?
- 同Module中path是否有重復?
- gradle任務message中是否有異常輸出?
- 路由映射是否注冊到UIRouter
- 是否url有誤?
- 是否有參與檢測的參數(shù),但是沒有包含或者有誤?(對于這一點還不清楚的,請等待第二篇文章)
- 生成的UIRouter是否在APPLike的onCreate生命周期節(jié)點中注冊到UIRouter
- 組件會維護兩份manifest文件,是否遺漏添加(注:異常已被Router捕獲并處理為:將目標加入黑名單,故沒有直觀的crash)
- Q:出現(xiàn)了ClassNotFoundException怎么辦?
A:應該是啟用了混淆,添加免混淆配置:可能因為項目變動的原因,后期會修改生成類path,一切以項目主頁為準
-keep class com.luojilab.router.** {*;}
-keep class com.luojilab.gen.** {*;}
-keep class * implements com.luojilab.component.componentlib.router.ISyringe {*;}
3.Q:出現(xiàn)了錯誤很難排查怎么辦?
A:根本原因是我當時和張老哥沒有協(xié)調好,導致1.0.0的代碼過早發(fā)出,而迭代版本因為其他原因遲遲未發(fā),四月份一定發(fā)版本,新版本中l(wèi)og的輸入以及防御性代碼比較完善,應該可以提供充足的糾錯信息。
4.Q:gradle plugin >=3.0 集成問題?
A:首先注意,我最開始就將依賴庫和注解處理庫分開了,這樣已經(jīng)避免了重復使用api和annotationProcessor聲明同一個庫的各種問題,如果在底層庫中集中添加注解依賴庫,使用api(或者還未移除的compile),不要使用Implementation;若是在組件Module中,隨意使用api或Implementation。但是必須使用annotationProcessor聲明注解處理庫(使用compile已經(jīng)不會自動添加到注解處理包路徑下)
- Q:kotlin是否可以用?
A:可以使用,如何集成參考demo,注意,對java和kt的Activity都使用注解,僅需要使用kapt聲明注解處理器即可,按照Demo集成kapt3即可,不需要聲明annotationProcessor,禁止使用早已廢棄的apt插件
(Q/A持續(xù)更新)
下一篇將詳細展開UIRouter的基礎功能特性、新版本特性,并會安排發(fā)一個迭代版本。
JIMU的討論群,群號693097923,歡迎大家加入:
qq群中有很多熱心的朋友,一些重要的討論,往往從群里面展開,最后轉移到項目的issue中展開討論以及總結。