自定義Android IOC框架

    IOC-控制反轉(Inversion of Control,英文縮寫爲IoC)是一個重要的面向對象編程的法則來削減計算機程序的耦合問題,也是輕量級的Spring框架的核心。 控制反轉通常分爲兩種類型,依賴注入(Dependency Injection,簡稱DI)和依賴查找(Dependency Lookup)。這段百度對IOC框架的解釋,對於Java開發者來說最著名的IOC框架莫過於Spring,而在咱們的Android開發中,IOC的使用更爲常見,好比你們常常使用的XUtil、butterKnife、EventBus、dagger、dagger二、otto等等,這些第三方庫幾乎都使用了IOC思想,舉個例子給你們:
    一般咱們在Activity中獲取一個圖片組件採用以下方法:css

ImageView img;
    img = findViewById(R.id.img);

    而使用IOC框架給我提供了一種基於註解的實現方式:java

@ViewInject(R.id.img) ImageView img;

    能夠看出這種方式彷佛更加簡潔
    其實這正是我本篇博文想給你們介紹的,IOC框架能夠:android

1.讓代碼更加簡潔
2.讓模板式的代碼更少,減小重複工做
3.把更多的精力放到業務邏輯上
4.解耦合

    下面我給你們詳細講解下如何自定義IOC框架,在Android中咱們使用IOC框架更可能是爲了方便注入全部的控件,好比說佈局文件。編程

自定義註解工具庫

總體庫結構

這裏寫圖片描述

定義註解工具類

佈局註解

/** * * 功能:自定義ContentView註解 * 做者:猴子搬來的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)// 使用在類上面
public @interface CastielContentViewInject {
    int value();// 定義一個方法去拿註解裏面的參數
}

組件註解

/** * * 功能:自定義View註解 * 做者:猴子搬來的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)// 使用在屬性字段上
public @interface CastielViewInject {
    int value();

事件註解

/** * * 功能:自定義OnClick註解 * 做者:猴子搬來的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)// 使用在方法上面
@CastielEventBase(listenerSetter="setOnClickListener",listenerType=View.OnClickListener.class,callbackMethod="onClick")
public @interface CastielOnClickInject {

    // 因爲有不少個點擊事件,因此要搞個數組
    int[] value();
}

在定義事件註解類時,咱們須要在這個註解的基礎上再定義一個註解,用於傳遞事件調用所需的三個重要元素setOnClickListener;傳接口類型;回調方法名字數組

事件註解基類

public @interface CastielEventBase {

    // 1.設置事件監聽的方法,配置方法的名字
    String listenerSetter();
    // 2.事件監聽的類型
    Class<?> listenerType();
    // 3.回調方法的名字
    String callbackMethod();
}

定義注入工具類

/** * 功能:InjectUtils注入工具類 * 做者:猴子搬來的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016/10/13 */ public class InjectUtils { public static void inject(Activity activity) { // 注入佈局 injectLayout(activity); // 注入視圖 injectViews(activity); // 注入事件 injectEvents(activity); } private static void injectEvents(Activity activity) { // 獲取方法上面的註解 Class<? extends Activity> myClass = activity.getClass(); Method myMethod[] = myClass.getDeclaredMethods();// 先拿到所有方法 for (Method method : myMethod) { Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); CastielEventBase ceb = annotationType.getAnnotation(CastielEventBase.class);// 拿到註解裏面的註解 // 獲得事件的三要素 String listenerSetter = ceb.listenerSetter(); Class<?> listenerType = ceb.listenerType(); String callbackMethod = ceb.callbackMethod(); // 獲取註解事件的控件對象Button try { Method valueMethod = annotationType.getDeclaredMethod("value"); try { int[] viewIds = (int[])valueMethod.invoke(annotation); for (int viewId : viewIds) { View view = activity.findViewById(viewId); // 反射setOnClickListener方法,這裏要用到代理 Method setListenerMethod = view.getClass().getMethod(listenerSetter, listenerType); Map<String, Method> methodMap = new HashMap<String, Method>(); methodMap.put(callbackMethod, method); InvocationHandler invocationHandler = new ListenerInvocationHandler(activity, methodMap); Object newProxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}, invocationHandler); setListenerMethod.invoke(view , newProxyInstance); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } } } private static void injectViews(Activity activity) { // 獲取每個屬性上的註解 Class<? extends Activity> myClass = activity.getClass(); Field[] myFields = myClass.getDeclaredFields();// 先拿到裏面全部的成員變量 for (Field field : myFields) { CastielViewInject myView = field.getAnnotation(CastielViewInject.class); if (myView != null) { int value = myView.value();// 拿到屬性id View view = activity.findViewById(value); // 將view賦值給類裏面的屬性 try { field.setAccessible(true);// 爲了防止其實私有的的,須要設置容許訪問 field.set(activity,view); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } } } } private static void injectLayout(Activity activity) { // 獲取咱們自定義類CastielContentViewInject上面的註解 Class<? extends Activity> myClass = activity.getClass(); CastielContentViewInject myContentView = myClass.getAnnotation(CastielContentViewInject.class); int myLayoutResId = myContentView.value(); activity.setContentView(myLayoutResId); } }

使用代理模式

爲了將咱們的方法點擊方法代替系統的點擊方法,咱們使用動態代理的方法去更替系統的點擊事件markdown

public class ListenerInvocationHandler implements InvocationHandler {

    Activity activity;
    Map<String,Method> methodMap;

    public ListenerInvocationHandler(Activity activity,Map<String,Method> methodMap) {
        this.activity = activity;
        this.methodMap = methodMap;
        Log.i("castiel", "打印方法Map:" + methodMap.toString());
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // method就是咱們的CastielOnClickInject
        String name = method.getName();
        Log.i("castiel", "打印方法name:" + name);
        Method mtd = methodMap.get(name);
        if (mtd != null) {
            return mtd.invoke(activity, args);
        }
        return method.invoke(activity, args);
    }

}

最後封裝Activity基類

/** * * 功能:封裝的Activity基類,引入咱們自定義的注入工具類 * 做者:猴子搬來的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */
public class BaseActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);
    }
}

使用自定義的註解工具庫

MainActivity.java框架

import com.castiel.ioc.activity.BaseActivity;
import com.castiel.ioc.annotation.CastielContentViewInject;
import com.castiel.ioc.annotation.CastielOnClickInject;
import com.castiel.ioc.annotation.CastielViewInject;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
@CastielContentViewInject(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @CastielViewInject(R.id.tv)
    private TextView tv;
    @CastielViewInject(R.id.btn)
    private Button btn;

    @CastielOnClickInject({R.id.btn})
    public void changText(){
        tv.setText("猴子搬來的救兵 http://blog.csdn.net/mynameishuangshuai");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

佈局文件ide

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" >

    <TextView  android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_margin="20dp" android:text="Hello world!" android:textSize="18sp" />

    <Button  android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv" android:layout_margin="20dp" android:text="按鈕" />

</RelativeLayout>

當我點擊」按鈕」,就能夠實現TextView文字的切換。工具

這裏寫圖片描述

這裏寫圖片描述

相關文章
相關標籤/搜索