Android_IOC容器實現(xiàn)View點擊注入

什么是IOC?

看了很多文章說IOC是依賴注入,或者說是控制反轉,其實這里有多個聯(lián)系比較緊密的概念,純理論概念的內容,感興趣可以大概了解下:

DIP 依賴倒置原則(Dependency Inverse Rrinciple)

強調系統(tǒng)的“高層組件”不應當依賴于“底層組件”,并且不論是“高層組件”還是“底層組件”
都應當依賴于抽象。抽象不應當依賴于實現(xiàn),實現(xiàn)應當依賴于抽象(軟件設計原則)

IOC 控制反轉( Inverse of Control)

一種反轉流、依賴和接口的方式。就是將控制權“往高處/上層”轉移,
控制反轉是實現(xiàn)依賴倒置的一種方法(DIP的具體實現(xiàn)方 式)

DI 依賴注入(-Dependency Injection)

組件通過構造函數(shù)或者setter方法,將其依賴暴露給.上層,上層要設法取得組件的依賴, 并將其傳遞給組件
依賴注入是實現(xiàn)控制反轉的一種手段(IloC的具 體實現(xiàn)方式)

IOC容器

依賴注入的框架,用來映射依賴,管理對象創(chuàng)建和生存周期(DI框架)

注: 本文主要是借IOC實現(xiàn)事件注入,簡單串一下幾個只是點,屬于比較基礎的點,所以寫的不是很詳細,都寫在備注上了:

①實現(xiàn)布局的注入

布局的注入比較簡單:
主要實現(xiàn)的是用注解的方式,取代Activity的 setContentView()方法:
創(chuàng)建一個注解,作用在類上(Demo中是Activity),用于取注解的值,也就是對應(Activity)要設置的布局的值
MainActivity只有兩個控件,一個TextView,一個Button,代碼就不貼了,有需看文末完整代碼,

/**
 * ContentView注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    //返回int類型的布局值
    int value();
}

MainActivity和BaseActivity中代碼比較簡單:
MainActivity

//使用ContentView注解
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}

BaseActivity

/**
 * desc   : BaseActivity
 */
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //幫助子類實現(xiàn)布局,控件,點擊事件注入
        InjectManager.inject(this);
    }
}

所以代碼邏輯主要在InjectManager中實現(xiàn),布局注入比較簡單,直接看代碼和備注吧,不做贅述,可以在Activity中的onResume()等方法中檢驗代碼是否有效~:

/**
 * desc   : InjectManager
 * 注解管理類
 */
public class InjectManager {

    /**
     * @param activity
     */
    public static void inject(Activity activity){
        injectLayout(activity);
    }

    /**
     * @param activity
     * 布局注入
     */
    public static void injectLayout(Activity activity){
        //獲取類
        Class<? extends Activity> clazz = activity.getClass();
        //獲取作用在類上的注解,這里取的是ContentView類型的注解
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView!=null) {
            //獲取ContentView注解的值,如Demo中Mainactivity取到的是 R.layout.activity_main
            int layoutId = contentView.value();
            try {
                //獲取setContentView方法
                Method method = clazz.getMethod("setContentView", int.class);
                //執(zhí)行setContentView方法
                method.invoke(activity,layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

②實現(xiàn)控件的注入

控件的注入就是為了通過注解取代findViewById方法,類似ButterKnife,只不過實現(xiàn)方式不同,ButterKnife采用的是APT,這里主要就是使用的反射,實現(xiàn)上首先定義個注解,并在MainActivity中使用,代碼如下:
InjectView注解

/**
 * desc   : InjectView
 * InjectView注解,取代findViewById
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value();
}

MainActivity中使用

  @InjectView(R.id.btn)
    Button btn;

具體實現(xiàn)實在InjectManager中增加了一個方法 用于處理控件的注入,這里上面的布局注入相差不大,主要注意下需要將findViewById方法的返回值賦值給View,具體可以看下注釋:

 /**
     * @param activity
     * 控件的注入
     */
    private static void injectView(Activity activity) {
        //獲取類
        Class<? extends Activity> clazz = activity.getClass();
        //獲取所有屬性,包括private等
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //獲取屬性上的InjectView類型的注解
            InjectView injectView = field.getAnnotation(InjectView.class);
            if (injectView != null) {
                //獲取注解的值,就是View的id值
                int viewId = injectView.value();
                try {
                    //獲取findViewById方法
                    Method method = clazz.getMethod("findViewById", int.class);
                    //執(zhí)行findViewById方法
                    Object view = method.invoke(activity, viewId);
                    //打開私有訪問權限,即private屬性可以修改
                    field.setAccessible(true);
                    //將findViewById方法的返回值賦值給控件
                    field.set(activity,view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

③點擊事件的注入

首先我們都知道Android的點擊事件和長按事件是這樣的:

//點擊事件
  btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
//長按事件
   btn.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
        });

可以看到他們的共同點

  • 監(jiān)聽方法名 setOnClickListener / setOnLongClickListener
  • 監(jiān)聽對象 View.OnClickListener() / View.OnLongClickListener()
  • 返回值 onClick(View v) / onLongClick(View v)
    那么我們定義一個EventBase注解需要將這是哪個特點體現(xiàn)出來,另外還要定義一個注解OnClick,EventBase作用在OnClick注解之上,而OnClick作用在回調的方法之上,就是響應View的點擊事件或者長按事件的回調,也就是最終要在MainActivity中觸發(fā)的方法~
    下面以注入點擊事件為例
    首先我們定義兩個注解:

EventBase注解主要是拿到三個必要信息,

/**
 * desc   : EventBase
 * 事件注入的注解
 */

@Target(ElementType.ANNOTATION_TYPE)//作用在其它注解之上
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
    //1.監(jiān)聽方法名 setOnClickListener / setOnLongClickListener
    String listenerSetter();

    //2.監(jiān)聽對象 View.OnClickListener() / View.OnLongClickListener()
    Class<?> listenerType();

    //3. 返回值  onClick(View v) /  onLongClick(View v)
    String callBackListener();
}
/**
 * desc   : OnClick
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnClickListener",listenerType= View.OnClickListener.class,callBackListener="onClick")
public @interface OnClick {
    int[] value();
}

然后在InjectManager中添加一個injectEvent的方法用于處理方法事件的注入,這里用到了動態(tài)代理,
目的是確定回調的方法,因為回調的方法有可能是onClick,也可能是onLongClick代碼如下:

 /**
     * @param activity
     * 事件注入
     */
    private static void injectEvent(Activity activity) {
        //獲取類
        Class<? extends Activity> clazz = activity.getClass();
        //獲取方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            //獲取方法上的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                //獲取注解的類型
                Class<? extends Annotation> annotationType = annotation.annotationType();
                if (annotationType != null) {
                    //獲取EventBase注解
                    EventBase eventBaseAnnotation = annotationType.getAnnotation(EventBase.class);
                    //獲取三個信息
                    if (eventBaseAnnotation!=null) {
                        //listenerSetter = setOnClickListener
                        String listenerSetter = eventBaseAnnotation.listenerSetter();
                        //listenerType = View.OnClickListener
                        Class<?> listenerType = eventBaseAnnotation.listenerType();
                        //
                        String callBackListener = eventBaseAnnotation.callBackListener();

                        try {
                            //獲取OnClick注解的value方法
                            Method valueMethod = annotationType.getDeclaredMethod("value");
                            //獲取OnClick注解的的值
                            int[] viewIds = (int[])valueMethod.invoke(annotation);
                            //代理的方式,根據(jù)注解類型,匹配對應的回調方法
                            //即View.OnClickListener 回調 onClick(View view)
                            //View.OnLongClickListener() 回調 onLongClick(View v)
                            ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(activity);
                            //添加需要攔截的方法,和替換執(zhí)行的方法
                            //callBackListener onClick
                            //method clickEvent
                            Log.e("callBackListener:",callBackListener+"");
                            Log.e("method:",method.getName()+"");
                            listenerInvocationHandler.addMethod(callBackListener,method);
                            Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);

                            for (int viewId : viewIds) {
                                //獲取findViewById方法
                                Method findViewMethod = clazz.getMethod("findViewById", int.class);
                                //執(zhí)行findViewById方法
                                View view = (View) findViewMethod.invoke(activity, viewId);

                                Method setter = view.getClass().getMethod(listenerSetter, listenerType);
                                setter.invoke(view,listener);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

貼一下代理的實現(xiàn):


/**
 * desc   : ListenerInvocationHandler
 * 代理的實現(xiàn)
 * AOP切面思想
 */
public class ListenerInvocationHandler implements InvocationHandler {
    //攔截的對象,可能是Activity可能是Fragment
    //這里是MainActivity的OnClick方法
    private Object target;
    //存儲方法名和方法
    //這里key:OnClick value : 預期回調的目標方法 clickEvent
    private HashMap<String,Method> methodHashMap=new HashMap<>();

    public ListenerInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (target!=null) {
            String name = method.getName();
            method=methodHashMap.get(name);//集合中有需要攔截的方法名直接攔截替換
            if (method!=null) {
                return method.invoke(target,args);
            }
        }
        return null;
    }

    /**
     * @param methodName 要攔截的方法名methodName:OnClick
     * @param method 需要替換執(zhí)行的方法method:clickEvent
     * 添加要攔截的方法
     */
    public void addMethod(String methodName,Method method){
        methodHashMap.put(methodName,method);
    }
}

另外OnLongClick的注解如下,和上面OnClick不同的是如果替換onLongClick方法,需要有個boolean返回值

/**
 * desc   : OnLongClick
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnLongClickListener",listenerType= View.OnLongClickListener.class,callBackListener="onLongClick")
public @interface OnLongClick {
    int[] value();
}

完整代碼可見:github

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容