Android 自定義一個ioc框架,快速完成開發(fā)

ioc框架現(xiàn)在成熟的已經(jīng)有很多了,xutils,butterknife等等都是ioc框架,利用這些框架我們可以快速的進行開發(fā)而不必重復(fù)寫一些代碼。但是在實際開發(fā)中我們往往有自己的需求,這時候我們就需要自定義一個ioc框架,根據(jù)需求我們可以在里面加入不同的注解簡化我們的操作。下面我們自己仿照butterknife的功能來自定義一個findViewByIdonClick()的ioc框架。
首先我們定義一個注解,使用@interface表示一個注解,代碼如下:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
    int value();
}

其中value()是我們使用注解的時候的參數(shù)值:

    @ViewById(R.id.test_tv)
    private TextView mTestTv;

就是上述代碼中的R.id.test_tv
@Target(ElementType.XXX)代表的是放在那個位置:
@Target(ElementType.TYPE) 接口、類、枚舉、注解
@Target(ElementType.FIELD) 字段、枚舉的常量
@Target(ElementType.METHOD) 方法
@Target(ElementType.PARAMETER)方法參數(shù)
@Target(ElementType.CONSTRUCTOR) 構(gòu)造函數(shù)
@Target(ElementType.LOCAL_VARIABLE)局部變量
@Target(ElementType.ANNOTATION_TYPE)注解
@Target(ElementType.PACKAGE)

@Retention(RetentionPolicy.XXX)代表什么時候檢測:
RetentionPolicy.RUNTIME代表運行時檢測,class文件中存在
RetentionPolicy.CLASS代表編譯時檢測,存在于class文件中,運行時無法獲取
RetentionPolicy.SOURCE代表在源文件中有效(在.java文件中有效)

接下來我們需要通過一個類來獲取這個注解,并且通過反射來拿到相應(yīng)的View,最后在賦值給該Filed:
代碼如下:

  package ioc.zzw.com.ioclibrary;

import android.app.Activity;
import android.view.View;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by zzw on 2017/5/4.
 * ioc注入工具類
 */

public class ViewUtils {

    public static void inject(Activity activity) {
        inject(new ViewHelper(activity), activity);
    }

    //為了兼容View
    public static void inject(View v) {
        inject(new ViewHelper(v), v);
    }

    //為了兼容Fragment
    public static void inject(View v, Object o) {
        inject(new ViewHelper(v), o);
    }


    /**
     * 最終都調(diào)用這個方法
     *
     * @param helper View的幫助類  通過這個類根據(jù)id找到相應(yīng)View
     * @param o      相關(guān)對象  Activity  view 等  從那個類傳進來的
     */
    private static void inject(ViewHelper helper, Object o) {
        injectField(helper, o);
        injectMethod(helper, o);
    }

    /**
     * 通過@ViewById得到id注入相應(yīng)的View
     *
     * @param helper View幫助類
     * @param o      相關(guān)對象  Activity  view 等  從那個類傳進來的
     */
    private static void injectField(ViewHelper helper, Object o) {
        //1.獲取到Object中所有的帶有@ViewById的字段
        Class<?> clazz = o.getClass();
        //獲取所有的字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //2.獲取到相應(yīng)的value值,也就是id值得到相應(yīng)的View
            ViewById viewById = field.getAnnotation(ViewById.class);
            if (viewById != null) {
                int viewId = viewById.value();
                View view = helper.findViewById(viewId);
                if (view != null) {
                    try {
                        //3.設(shè)置字段值,也就是給字段賦值
                        field.setAccessible(true);//為了使不被修飾符梭影響
                        field.set(o, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 設(shè)置點擊時間
     *
     * @param helper View幫助類
     * @param o      相關(guān)對象  Activity  view 等  從那個類傳進來的
     */
    private static void injectMethod(ViewHelper helper, Object o) {
        //1.獲取到所有帶有@OnClick的方法
        Class<?> clazz = o.getClass();
        Method[] methods = clazz.getDeclaredMethods();//獲取所有方法
        for (Method method : methods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null) {
                //2.獲取到相應(yīng)的value值,也就是要設(shè)置點擊時間的id數(shù)組
                int[] values = onClick.value();
                for (int viewId : values) {
                    //3.通過id獲取到相應(yīng)的Vie,然后設(shè)置點擊事件
                    View view = helper.findViewById(viewId);
                    if (view != null) {
                        view.setOnClickListener(new DeclaredOnClickListener(method, o));
                    }
                }
            }
        }
    }

    private static class DeclaredOnClickListener implements View.OnClickListener {

        //設(shè)置點擊事件的方法
        private Method mMethod;
        //在那個類中
        private Object mObject;


        public DeclaredOnClickListener(Method method, Object o) {
            this.mMethod = method;
            this.mObject = o;
        }

        @Override
        public void onClick(View v) {
            try {
                mMethod.setAccessible(true);//所有修飾符都可以搞事
                mMethod.invoke(mObject, v);//可以避免點擊閃退
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mObject, (Object[]) null);//當(dāng)方法體里面沒有參數(shù)時候調(diào)用改方法,執(zhí)行沒有方法體的修飾的函數(shù)
                } catch (IllegalAccessException e1) {
                    e1.printStackTrace();
                } catch (InvocationTargetException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }


}

package ioc.zzw.com.ioclibrary;

import android.app.Activity;
import android.support.annotation.IdRes;
import android.view.View;

/**
 * Created by zzw on 2017/5/4.
 * View幫助類
 */

public class ViewHelper {

    private Activity mActivity;
    private View mView;

    public ViewHelper(Activity activity) {
        this.mActivity = activity;
    }

    public ViewHelper(View v) {
        this.mView = v;
    }

    public View findViewById(@IdRes int id) {

        return mActivity == null ? mView.findViewById(id) : mActivity.findViewById(id);
    }
}

package ioc.zzw.com.ioclibrary;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Created by zzw on 2017/5/4.
*
*/

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewById {
   int value();
}

  package ioc.zzw.com.ioclibrary;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by zzw on 2017/5/4.
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
    int []value();
}

package com.zzw.ioc;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import ioc.zzw.com.ioclibrary.OnClick;
import ioc.zzw.com.ioclibrary.ViewById;
import ioc.zzw.com.ioclibrary.ViewUtils;

public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.test_tv)
    private TextView mTestTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);
        mTestTv.setText("ioc_");
    }


    @OnClick({R.id.test_tv, R.id.test_button})
    private void onClick(View view) {
        switch (view.getId()) {
            case R.id.test_tv:
                Toast.makeText(this, "點擊了TextView", Toast.LENGTH_SHORT).show();
                break;

            case R.id.test_button:
                Toast.makeText(this, "點擊了Button", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

我們可以根據(jù)需求做一些自定義需要的注解,這里用網(wǎng)絡(luò)檢測來做個例子,當(dāng)點擊了檢測一下網(wǎng)絡(luò),如果沒有網(wǎng)絡(luò)的話就顯示網(wǎng)絡(luò)的提示,有的話就繼續(xù)往下執(zhí)行:
我們加上CheckNet

package ioc.zzw.com.ioclibrary;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by zzw on 2017/5/4.
 * 點擊之后是否要執(zhí)行網(wǎng)絡(luò)檢測
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
    String value() default "親,您的網(wǎng)絡(luò)鏈接有問題哦!";
}

ViewUtils類中點擊事件注入的時候加上判斷:

/**
     * 設(shè)置點擊事件
     *
     * @param helper View幫助類
     * @param o      相關(guān)對象  Activity  view 等  從那個類傳進來的
     */
    private static void injectMethod(ViewHelper helper, Object o) {
        //1.獲取到所有帶有@OnClick的方法
        Class<?> clazz = o.getClass();
        Method[] methods = clazz.getDeclaredMethods();//獲取所有方法
        for (Method method : methods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null) {
                //網(wǎng)絡(luò)檢測
                CheckNet checkNet = method.getAnnotation(CheckNet.class);
                String hint = null;
                if (checkNet != null) {
                    hint = checkNet.value();
                }

                //2.獲取到相應(yīng)的value值,也就是要設(shè)置點擊時間的id數(shù)組
                int[] values = onClick.value();
                for (int viewId : values) {
                    //3.通過id獲取到相應(yīng)的Vie,然后設(shè)置點擊事件
                    View view = helper.findViewById(viewId);
                    if (view != null) {
                        view.setOnClickListener(new DeclaredOnClickListener(method, o, hint));
                    }
                }
            }
        }
    }

    private static class DeclaredOnClickListener implements View.OnClickListener {

        //設(shè)置點擊事件的方法
        private Method mMethod;
        //在那個類中
        private Object mObject;
        //是否檢查網(wǎng)絡(luò)
        private String mNoNetHint;

        public DeclaredOnClickListener(Method method, Object o, String hint) {
            this.mMethod = method;
            this.mObject = o;
            this.mNoNetHint = hint;
        }

        @Override
        public void onClick(View v) {
            try {
                mMethod.setAccessible(true);//所有修飾符都可以搞事
                if (mNoNetHint != null && !isNetConnected(v.getContext())) {
                    Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
                    return;
                }
                mMethod.invoke(mObject, v);//可以避免點擊閃退
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mObject, (Object[]) null);//當(dāng)方法體里面沒有參數(shù)時候調(diào)用改方法,執(zhí)行沒有方法體的修飾的函數(shù)
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }


    /**
     * 檢測網(wǎng)絡(luò)是否連接
     *
     * @return
     */
    private static boolean isNetConnected(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm != null) {
            NetworkInfo[] infos = cm.getAllNetworkInfo();
            if (infos != null) {
                for (NetworkInfo ni : infos) {
                    if (ni.isConnected()) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

這樣,我們在需要判斷的時候加上這個注解:

    @OnClick({R.id.test_tv, R.id.test_button})
    @CheckNet("您網(wǎng)絡(luò)有問題哦!")
    private void onClick(View view) {
        switch (view.getId()) {
            case R.id.test_tv:
                Toast.makeText(this, "點擊了TextView", Toast.LENGTH_SHORT).show();
                break;

            case R.id.test_button:
                Toast.makeText(this, "點擊了Button", Toast.LENGTH_SHORT).show();
                break;
        }
    }

這樣的話就能夠提示你需要的文字

如果你直接這樣:

    @OnClick({R.id.test_tv, R.id.test_button})
    @CheckNet
    private void onClick(View view) {
        switch (view.getId()) {
            case R.id.test_tv:
                Toast.makeText(this, "點擊了TextView", Toast.LENGTH_SHORT).show();
                break;

            case R.id.test_button:
                Toast.makeText(this, "點擊了Button", Toast.LENGTH_SHORT).show();
                break;
        }
    }

這樣的話就會提示默認(rèn)的文字。

最后加上一個小技巧:
在我們利用反射來賦值或者執(zhí)行方法的時候一般會try catch,這個時候我們把異常改為Exception,這樣的話就能避免閃退,這樣用戶體驗增加了,但是會有一些小問題,比如點擊的時候出了異常,這時候點擊就沒反應(yīng),所以我們要細(xì)心查看logcat打印信息找到問題所在,從而解決問題。就是想下面這幾段代碼一樣:

                try {
                        //3.設(shè)置字段值,也就是給字段賦值
                        field.setAccessible(true);//為了使不被修飾符梭影響
                        field.set(o, view);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

         try {
                mMethod.setAccessible(true);//所有修飾符都可以搞事
                if (mNoNetHint != null && !isNetConnected(v.getContext())) {
                    Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
                    return;
                }
                mMethod.invoke(mObject, v);//可以避免點擊閃退
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mObject, (Object[]) null);//當(dāng)方法體里面沒有參數(shù)時候調(diào)用改方法,執(zhí)行沒有方法體的修飾的函數(shù)
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }


demo點此下載
學(xué)習(xí)來源:紅橙Darren

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容