自定義 Android IOC 框架
更多關於Java的技術和資訊可以關注我的專欄:
Android高級技術分享專欄免費給大家分享Android架構的學習資料和視頻
概述
什麼是 IOC
Inversion of Control,英文縮寫為 IOC,意思為控制反轉。
具體什麼含義呢?
假設一個類中有很多的成員變數,如果你需要用到裡面的成員變數,傳統做法是 new 出來進行使用,但是在 IOC 的原則中,我們不要 new,因為這樣的耦合度太高,我們可以在需要注入(new)的成員變數上添加註解,等待載入這個類的時候,則進行注入。
那麼怎麼進行注入呢?
簡單的說,就是通過反射的方式,將字元串類路徑變為類。
什麼是反射
JAVA 並不是一種動態變成語言,為了使語言更加靈活,JAVA 引入了反射機制。JAVA 反射機制是在運行過程中,對於任意一個類,都能知道這個類的所有屬性和方法;對於任意一個屬性,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成為 JAVA 語言的反射機制。
什麼是註解
JAVA 1.5 之後引入的註解和反射,註解的實現依賴於反射。JAVA 中的註解是一種繼承自介面 java.lang.annotation.Annotation 的特殊介面。
那麼介面怎麼能夠設置屬性呢?
簡單來說就是 JAVA 通過動態代理的方式為你生成了一個實現了介面 Annotation 的實例,然後對該代理實例的屬性賦值,這樣就可以在程序運行時(如果將註解設置為運行時可見的話)通過反射獲取到註解的配置信息。說的通俗一點,註解相當於一種標記,在程序中加了註解就等於為程序打上了某種標記。程序可以利用JAVA的反射機制來了解你的類及各種元素上有無何種標記,針對不同的標記,就去做相應的事件。標記可以加在包,類,方法,方法的參數以及成員變數上。
實現
定義註解
- 布局註解
/**
* Created by Keven on 2019/6/3.
*
* 布局註解
*/
//RUNTIME 運行時檢測,CLASS 編譯時檢測 SOURCE 源碼資源時檢測
@Retention(RetentionPolicy.RUNTIME)
//TYPE 用在類上 FIELD 註解只能放在屬性上 METHOD 用在方法上 CONSTRUCTOR 構造方法上
@Target(ElementType.TYPE)
public @interface KevenContentViewInject {
int value();
//代表可以 Int 類型,取註解裡面的參數
}
2. 屬性組件註解
/**
* Created by Keven on 2019/6/3.
*
* 組件註解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//用在屬性欄位上
public @interface KevenViewInject {
int value();
}
3. 事件註解
/**
* Created by zhengjian on 2019/6/3.
*
* 事件註解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//使用在方法上
public @interface KevenOnClickInject {
//會有很多個點擊事件,所以使用數組
int[] value();
}
實現注入工具類
/**
* Created by Keven on 2019/6/3.
* <p>
* InjectUtils 注入工具類
*/
public class InjectUtils {
//注入方法 Activity
public static void inject(Activity activity) {
injectLayout(activity);
injectViews(new ViewFinder(activity), activity);
injectEvents(new ViewFinder(activity), activity);
}
//注入方法 View
public static void inject(View view, Activity activity) {
injectViews(new ViewFinder(view), activity);
injectEvents(new ViewFinder(view), activity);
}
//注入方法 Fragment
public static void inject(View view, Object object) {
injectViews(new ViewFinder(view), object);
injectEvents(new ViewFinder(view), object);
}
/**
* 事件注入
*/
private static void injectEvents(ViewFinder viewFinder, Object object) {
// 1.獲取所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
// 2.獲取方法上面的所有id
for (Method method : methods) {
KevenOnClickInject onClick = method.getAnnotation(KevenOnClickInject.class);
if (onClick != null) {
int[] viewIds = onClick.value();
if (viewIds.length > 0) {
for (int viewId : viewIds) {
// 3.遍歷所有的id 先findViewById然後 setOnClickListener
View view = viewFinder.findViewById(viewId);
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, object));
}
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
private Method mMethod;
private Object mHandlerType;
public DeclaredOnClickListener(Method method, Object handlerType) {
mMethod = method;
mHandlerType = handlerType;
}
@Override
public void onClick(View v) {
// 4.反射執行方法
mMethod.setAccessible(true);
try {
mMethod.invoke(mHandlerType, v);
}
catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mHandlerType, null);
}
catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
//控制項注入
private static void injectViews(ViewFinder viewFinder, Object object) {
//獲取每一個屬性上的註解
Class<?> myClass = object.getClass();
Field[] myFields = myClass.getDeclaredFields();
//先拿到所有的成員變數
for (Field field : myFields) {
KevenViewInject myView = field.getAnnotation(KevenViewInject.class);
if (myView != null) {
int value = myView.value();
//拿到屬性id
View view = viewFinder.findViewById(value);
//將view 賦值給類裡面的屬性
try {
field.setAccessible(true);
//為了防止其是私有的,設置允許訪問
field.set(object, view);
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private static void injectLayout(Activity activity) {
//獲取我們自定義類KevenContentViewInject 上面的註解
Class<?> myClass = activity.getClass();
KevenContentViewInject myContentView = myClass.getAnnotation(KevenContentViewInject.class);
if (myContentView!=null){
int myLayoutResId = myContentView.value();
activity.setContentView(myLayoutResId);
}
}
}
定義 ViewFinder 類
用於注入工具類中的 findViewById
/**
* Created by Keven on 2019/6/3.
*/
final class ViewFinder {
private View view;
private Activity activity;
public ViewFinder(View view) {
this.view = view;
}
public ViewFinder(Activity activity) {
this.activity = activity;
}
public View findViewById(int id) {
if (view != null) return view.findViewById(id);
if (activity != null) return activity.findViewById(id);
return null;
}
public View findViewById(int id, int pid) {
View pView = null;
if (pid > 0) {
pView = this.findViewById(pid);
}
View view = null;
if (pView != null) {
view = pView.findViewById(id);
} else {
view = this.findViewById(id);
}
return view;
}
/*public Context getContext() {
if (view != null) return view.getContext();
if (activity != null) return activity;
return null;
}*/
}
使用 IOC 框架
布局文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width_="match_parent"
android:layout_height="match_parent"
tools:context=".ioc.IocActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width_="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen_35dp"
android:text="你好,IOC"
android:textSize="25sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/bt_pop"
android:layout_width_="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen_35dp"
android:text="彈窗"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"/>
</android.support.constraint.ConstraintLayout>
Activity 代碼
//布局文件注入
@KevenContentViewInject(R.layout.activity_ioc)
public class IocActivity extends AppCompatActivity {
//屬性控制項注入
@KevenViewInject(R.id.tv_title)
private TextView tv_title;
@KevenViewInject(R.id.bt_pop)
private Button bt_pop;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//注入工具綁定
InjectUtils.inject(this);
}
//點擊事件注入
@KevenOnClickInject(R.id.bt_pop)
public void change(){
tv_title.setText("hello IOC");
Toast.makeText(this,"Hello IOC",Toast.LENGTH_sHORT).show();
}
}
當我們點擊彈窗按鈕時,上方 TextView 內容會改變,並且有 Toast 彈出。