1. 程式人生 > >Android 自己打造IOC註解框架

Android 自己打造IOC註解框架

Android中IOC框架就是注入控制元件和佈局或者說是設定點選監聽,網上有很多成熟的註解框架例如xUtils,afinal,butterknife等等。你可能會問,既然已經有好的框架為何還要造輪子?因為,首先我是學習,學習框架的設計以及實現,其次是拓展,適合自己的輪子才是好輪子,所以我添加了判斷網路狀態的註解。此處特別感謝輝哥,他的技術分享是我的楷模。

首先看看最終完成的效果

public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.tv_hello)
    private TextView tv_hello;

    @ViewById
(R.id.iv_img) private ImageView iv_img; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewUtils.inject(this); tv_hello.setText("你好,IOC"); } @OnClick({R.id.tv_hello, R.id.iv_img}) @CheckNet
private void sayHello(){ Toast.makeText(this, tv_hello.getText().toString(), Toast.LENGTH_SHORT).show(); } }

執行效果
有網和無網狀態

有網狀態下,可以正常點選然後提示,無網狀態下提示網路的問題。

下面看看具體實現
我閱讀了xUtils和butterknife的原始碼,xUtils使用有點麻煩,butterknife的定義(變數,方法等)只能是public,所以我結合兩者,造出適合自己的,習慣是private定義,以及追求使用方便。

首先要了解一下反射機制,不懂的話百度一下
好。coding
屬性注入 : 利用反射去 獲取Annotation –> value –> findViewById –> 反射注入屬性
事件注入 :利用反射去 獲取Annotation –> value –> findViewById –> setOnclickListener –> 動態代理反射執行方法

新建一個Module,新增以下的類

View註解的Annotation

/**
 * @creation_time: 2017/5/7
 * @author: Vegen
 * @e-mail: [email protected]
 * @describe: View註解的Annotation
 */
//@Target(ElementType.FIELD) 代表Annotation的位置  FIELD代表屬性 TYPE類上  CONSTRUCTOR建構函式上  METHOD方法上面
@Target(ElementType.FIELD)
//@Retention(RetentionPolicy.CLASS) 什麼時候生效 CLASS代表編譯時 RUNTIME執行時 SOURCE原始碼資源
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
    //@ViewById(R.id,xxx)
    int value();
}

View輔助類

/**
 * @creation_time: 2017/5/7
 * @author: Vegen
 * @e-mail: [email protected]
 * @describe: View輔助類
 */

public class ViewFinder {
    private Activity activity;
    private View view;

    public ViewFinder(Activity activity) {
        this.activity = activity;
    }

    public ViewFinder(View view) {
        this.view = view;
    }

    public View findViewById(int viewId){
        return activity != null ? activity.findViewById(viewId) : view.findViewById(viewId);
    }
}

View工具類

/**
 * @creation_time: 2017/5/7
 * @author: Vegen
 * @e-mail: [email protected]
 * @describe: View工具類
 */

public class ViewUtils {
    public static void inject(Activity activity){
        inject(new ViewFinder(activity), activity);
    }
    public static void inject(View view){
        inject(new ViewFinder(view), view);
    }
    public static void inject(View view, Object object){
        inject(new ViewFinder(view), object);
    }
    // 相容上面三個方法   object是反射需要執行的類
    public static void inject(ViewFinder finder, Object object){
        // 注入屬性
        injectFile(finder, object);
        // 注入事件
        injectEvent(finder, object);
    }

    /**
     * 事件注入
     * @param finder
     * @param object
     */
    private static void injectEvent(ViewFinder finder, Object object) {
        // 1、獲取類的所有方法
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        // 2、獲取OnClick裡面的value值
        for (Method method : methods){
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null){
                int[] viewIds = onClick.value();
                for (int viewId : viewIds){
                    // 3、findViewById找到View
                    View view = finder.findViewById(viewId);

                    //拓展功能 檢測網路
                    boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;

                    if (view != null){
                        // 4、setOnClickListener
                        view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet));
                    }
                }
            }
        }
    }

    private static class DeclaredOnClickListener implements View.OnClickListener{

        private Object mObject;
        private Method mMethod;
        private boolean mIsCheckNet;

        public DeclaredOnClickListener(Method method, Object object, boolean isCheckNet) {
            this.mMethod = method;
            this.mObject = object;
            this.mIsCheckNet = isCheckNet;
        }

        @Override
        public void onClick(View view) {
            //判斷是否需要檢測網路
            if (mIsCheckNet){
                //需要
                if (!isNetConnected(view.getContext())){
                    Toast.makeText(view.getContext(), "請檢查網路,請稍後重試", Toast.LENGTH_SHORT).show();
                    return;
                }
            }

            //點選會呼叫該方法
            try {
                // 所有方法都可以 包括私有公有
                mMethod.setAccessible(true);
                // 5、反射注入執行方法
                mMethod.invoke(mObject, view);
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mObject, null);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 檢測手機資料是否可用
     */
    public static boolean isMobileNetConnected(Context context){
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
        return cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isConnected();
    }

    /**
     * 檢測WIFI是否開啟可用
     */
    public static boolean isWifiNetConnected(Context context){
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
        return cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected();
    }

    /**
     * 檢測網路是否可用
     */
    public static boolean isNetConnected(Context context){
        return isMobileNetConnected(context) || isWifiNetConnected(context);
    }

    /**
     * 注入屬性
     * @param finder
     * @param object
     */
    private static void injectFile(ViewFinder finder, Object object) {
        // 1、獲取類裡面的所有的屬性
        Class<?> clazz = object.getClass();
        // 獲取所有屬性包括私有和公有
        Field[] fields = clazz.getDeclaredFields();

        // 2、獲取ViewById裡面的value值
        for (Field field : fields){
            ViewById viewById = field.getAnnotation(ViewById.class);
            if (viewById != null){
                // 獲取註解裡面的id值
                int viewId = viewById.value();

                // 3、findViewById找到View
                View view = finder.findViewById(viewId);
                if (view != null) {
                    // 能夠注入所有的修飾符 private public
                    field.setAccessible(true);

                    // 4、動態注入找到的View
                    try {
                        field.set(object, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }



    }
}

View 事件註解的Annotation

/**
 * @creation_time: 2017/5/7
 * @author: Vegen
 * @e-mail: [email protected]
 * @describe: View 事件註解的Annotation
 */
//@Target(ElementType.FIELD) 代表Annotation的位置  FIELD代表屬性 TYPE類上  CONSTRUCTOR建構函式上  METHOD方法上面
@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.CLASS) 什麼時候生效 CLASS代表編譯時 RUNTIME執行時 SOURCE原始碼資源
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    //@ViewById(R.id,xxx)
    int[] value();
}

View 網路狀態註解的Annotation

/**
 * @creation_time: 2017/5/7
 * @author: Vegen
 * @e-mail: [email protected]
 * @describe: View 網路狀態註解的Annotation
 */
//@Target(ElementType.FIELD) 代表Annotation的位置  FIELD代表屬性 TYPE類上  CONSTRUCTOR建構函式上  METHOD方法上面
@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.CLASS) 什麼時候生效 CLASS代表編譯時 RUNTIME執行時 SOURCE原始碼資源
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNet {
}

好,到此為止已經完成註解框架了,下面看看使用
MainActivity

public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.tv_hello)
    private TextView tv_hello;

    @ViewById(R.id.iv_img)
    private ImageView iv_img;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);
        tv_hello.setText("你好,IOC");
    }

    @OnClick({R.id.tv_hello, R.id.iv_img})
    @CheckNet
    //方法名隨意
    private void sayHello(){
        Toast.makeText(this, tv_hello.getText().toString(), Toast.LENGTH_SHORT).show();
    }

}

佈局檔案activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="com.vegen.study.ioctest.MainActivity"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/iv_img"
        android:layout_marginTop="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>

</LinearLayout>

別忘了新增網路訪問許可權

<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

具體程式碼以及demo已經開源到我的github,歡迎star,有問題交流一下,地址:Vegen的github