組件化中使用動態創建的作用是解耦
一.反射基礎:
反射機制是在運行狀態中,對於任意一個類,能夠知道這個類的所有屬性和方法。反射是可以在一個類運行的時候獲取類的信息的機制,可以獲取在編譯期不可能獲取類的信息。對於任何一個對象,能夠調用它的任意一個方法和屬性。因爲類的信息是保存在Class對象中的,而這個Class對象是在程序運行時被類加載(ClassLoader)動態加載的。當類加載器裝載運行了類後,動態獲取了Class對象的信息以及動態操作Class對象的屬性和方法稱爲Java語言的反射機制。
實現反射,實際上是得到Class對象,使用java.lang.Class這個類。這是Java反射機制的起源,當一個類被加載後,Java虛擬機會自動產生一個Class對象。
- 反射機制獲取類:
Class clazz = Class.forName("com.example.demo1.demo.RxBus");
Class clazz1 = RxBus.class;
Class clazz2 = new RxBus().getClass();
第一種Class.forName()方式,會讓ClassLoader裝載類,並進行類的初始化;
第二種class方式,ClassLoader裝載入內存,不對類進行類的初始化操作;
第三種getClass()方式,返回類對象運行時真正所指的對象/所屬類型的Class對象。
- 無參數創建對象:
Class clazz = Class.forName("com.example.demo1.demo.RxBus");
Object object = clazz.newInstance();
newInstance()是使用類的加載機制,創建一個實例。這個類已經被類加載,並已經被連接,這是因爲forName會讓ClassLoader裝載類和進行類的初始化工作,其實際是創建一個Object對象。
- 有參數創建對象:
Class clazz = Class.forName("com.example.demo1.demo.RxBus");
Constructor csr = clazz.getConstructor(String.class,int.class);
Object object = csr.newInstance("jiji",28);
這getContructor方法會返回一個Constructor對象,它反映了Class對象所表示的類指定的公共構造方法。
- 反射類中的屬性需要使用Field對象:
Field field = clazz.getField("方法名");
- 修改屬性中的修飾符:
Field field = clazz.getDeclaredField("方法名");
String priv = Modifier.toString(field.getModifiers());
- 反射類中的方法:
Method method = clazz.getDeclaredMethod("setString", String.class);
method.invoke(clazz,"nihao");
getDeclaredMethod()獲取的是類自身聲明的所有方法,包含public/protected/private方法。Method中invoke方法用於檢查AccessibleObject的override屬性是否爲true。
二.反射進階:
- 獲取不到Class:
當Class.forName()中路徑獲取不到對應的Class是,會拋出異常。
- 獲取不到Field:
一種是不存在這個Field,另一種是修飾符導致的權限問題。getField只能獲取對象中的public修飾符的屬性,並且能獲取父類Class的public屬性;getDeclaredField能獲取對象中各種修飾符的屬性,但無法獲取父類的任何屬性。
- 獲取不到Method:
與Field相類似,當方法名或參數數目類型沒對上,就會拋出異常。
- 獲取不到Contructor:
構造方式無法調用到父類的任何構造方法,反射創建一個對象,可以使用Class.newInstance()和Contructor.newInstance()兩種方法,不同之處在於Class.newInstance()的使用受到嚴格限制,對應的Class中必須存在一個無參數的構造方法,並且必須要有訪問權限。而Contructor.newInstance()適應任何類型的構造方法,無論是否有參數都可以調用,只需要使用setAccessible()控制訪問驗證即可。
- 反射靜態方法:
調用靜態方法直接用Class.method()的形式就可以調用。
- 反射泛型參數方法:
泛型的基礎是類型的擦除,當有一個方法中有泛型參數時,編譯器會自動類型向上轉型,最終向上轉型就成爲Object。所以getDeclaredMethod需要用Object.class作爲參數。
- Proxy動態代理機制:
代理模式的作用是爲了其他對象提供一種代理控制對這個對象的訪問,是控制器訪問的方式,而不只是對方法擴展。
- 聲明一個共同的接口Subject:
public interface Subject {
void doSomething();
}
- 具體實現類RealSubject:
public class RealSubject implements Subject {
@Override
public void doSomething() {
// TODO: 2019-08-25 do something
}
}
- 實現InvocationHandler,做一些居圖的邏輯處理:
public class ProxyHandler implements InvocationHandler {
private Object realSubject;
public ProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//在轉調具體目標對象之前,可以執行一些功能處理
//調用具體對象的方法
Object result = method.invoke(realSubject);
//在轉調具體目標對象之後,可以執行一些功能處理
return result;
}
}
- 通過Proxy新建代理類對象:
RealSubject realSubject = new RealSubject();
Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader()
,new Class[]{Subject.class}
,new ProxyHandler(realSubject)
);
proxySubject.doSomething();
動態代理的作用在於不修改源碼的情況下,可以增強一些方法,在方法執行前後做任何想做的事情。
三.反射簡化jOOR:
越來越多人使用jOOR反射框架,其具體文件只有兩個Java文件,非常輕量,並且內部封裝好了異常拋出,讓代碼編寫更加優雅。
使用的方式也是熟悉的鏈式調用:
import static org.joor.Reflect.*;
String world = on("java.lang.String") // Like Class.forName()
.create("Hello World") // Call most specific matching constructor
.call("substring", 6) // Call most specific matching substring() method
.call("toString") // Call toString()
.get(); // Get the wrapped object, in this case a String
並且支持動態代理:
public interface StringProxy {
String substring(int beginIndex);
}
String substring = on("java.lang.String")
.create("Hello World")
.as(StringProxy.class) // Create a proxy for the wrapped object
.substring(6); // Call a proxy method
四.動態創建Fragment:
Fragment做成佈局的承載,其擁有自身的聲明週期,但自身無法獨立分離Activity這是特殊的使用限制。
這種限制在我們正常使用Activity引用Fragment方式是強引用,需要使用import包名來完成。假如移除module,那麼引用Fragment的module將會提示索引不到資源包。
錄製APP的例子,使用PageConfig來封裝這些配置。然後通過反射機制創建出Fragment,並添加到ViewPager中。如Fragment所在的module被整體移除,因爲索引不到Fragment,則會捕獲到異常,而不會造成奔潰。
使用反射相對會安全一點,也會降低耦合,但反射回造成一定的效率下降。
使用跨模塊獲取Fragment非常適合在單一Activity+Fragment的app框架中使用。因爲Fragment劃分模塊作爲入口設計,使用ARouter的方式非常適應模塊間解耦的要求。
如想要把初始化的方法都解耦到每個module中,需要跨module調用方法,剛好ARouter也提供了跨模塊調用對象方法的方案:
- 擴展IProvider接口,創建一個初始化參數的接口:
- 繼承接口並實現初始化Fragment的方法:
- 通過ARouter和IProvider接口和路由地址調用初始化Fragment的方法:
五.動態配置Application:
如某些功能模塊中需要做一些初始化的操作,只能強引用到主module的Application中,是否有方法可以降低耦合性?
這裏有兩種配置Application的思路:
第一種是通過module的Application獲取各個module的初始化文件,然後通過反射初始化的Java文件來調用初始化方法:
- 在Base module中定義接口BaseAppInt,裏面有兩個方法,onInitSpeed的內容最快被Application初始化,onInitLow的內容可以等其他的Application都被初始化後再調用;
public interface BaseAppInt {
boolean onInitSpeed(Application application);
boolean onInitLow(Application application);
}
- 在module中使用BaseAppInt接口,在類中實現操作;
public class NewInit implements BaseAppInt {
@Override
public boolean onInitSpeed(Application application) {
// TODO: 2019-08-25 初始化操作
return false;
}
@Override
public boolean onInitLow(Application application) {
// TODO: 2019-08-25 初始化操作
return false;
}
}
- 在PageConfig中配置;
public class PageConfig {
private static final String NewsInit = "NewInit所在的包名地址";
public static String[] initModules = {
NewsInit
};
}
- 在主module的Application中實現兩個初始化的方法;
public class BaseApplication extends Application {
public void initModuleSpeed() {
for (String init : PageConfig.initModules) {
try {
Class<?> clszz = Class.forName(init);
BaseAppInt moduleInit = (BaseAppInt) clszz.newInstance();
moduleInit.onInitSpeed(this);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
public void initModuleLow() {
for (String init : PageConfig.initModules) {
try {
Class<?> clszz = Class.forName(init);
BaseAppInt moduleInit = (BaseAppInt) clszz.newInstance();
moduleInit.onInitLow(this);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}
- 在Application中的onCreate中調用;
public class GankApplication extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
initModuleSpeed();
// TODO: 2019-08-25 其他初始化的操作
initModuleLow();
}
}
使用這種反射方法完成初始化操作,基本可以滿足組件化中 的需求和解耦需求,但是反射會帶來一定的性能損耗。對於追求app秒開體驗的需求,可以通過RxJava簡單地使用非UI線程實現,減少對UI線程的阻塞,這種初始化操作儘量是飛非UI的操作。
第二種方式可以選擇初始化延後的方式,因爲某些模塊的初始化操作不一定在Application啓動時立即執行。可以採用延後帶MainActivity初始化後的方式,保證在相關模塊做使用前的懶加載;這種方式時通過主module的Application中繼承Base module的Application來實現的,主module的Application將註冊每個module的初始化文件,然後通過Base module中的Application來對初始化文件做啓動封裝。
- 在Base module中聲明一個初始類;
public class BaseAppLogic {
protected BaseApplication mBaseApplication;
public BaseAppLogic() {
}
public void setApplication(@NonNull BaseApplication base1Application) {
this.mBaseApplication = base1Application;
}
public void onCreate() {
}
public void onTerminate() {
}
public void onLowMemory() {
}
public void onTrimMemory() {
}
public void onConfigurationChanged(Configuration configuration) {
}
}
- 在Base module中添加Application的註冊和運行邏輯;
public abstract class BaseApplication extends Application {
private List<Class<? extends BaseAppLogic>> logicList = new ArrayList<>();
private List<BaseAppLogic> logicClassList = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
initLogic();
logicCreate();
}
protected void registerApplicationLogic(Class<? extends BaseAppLogic> logicClass) {
logicList.add(logicClass);
}
protected void logicCreate() {
for (Class<? extends BaseAppLogic> logicClass : logicList) {
try {
BaseAppLogic appLogic = logicClass.newInstance();
logicClassList.add(appLogic);
appLogic.onCreate();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
protected abstract void initLogic(); //主module的Application調用
@Override
public void onTerminate() {
super.onTerminate();
for (BaseAppLogic logic : logicClassList) {
logic.onTerminate();
}
}
}
- 每個module需要初始化時即成此類,然後複寫需要的接口;
public class NewInitLogic extends BaseAppLogic {
@Override
public void onCreate() {
super.onCreate();
}
}
- 在主module的Application調用initLogic註冊所有的BaseAppLogic class類。
public class GankApplication extends BaseApplication {
@Override
protected void initLogic() {
registerApplicationLogic(NewInitLogic.class);
}
}