1. 程式人生 > 其它 >紅橙Darren視訊筆記 IOC註解框架 自己寫個註解框架

紅橙Darren視訊筆記 IOC註解框架 自己寫個註解框架

技術標籤:架構篇1上

目標

目標就是不需要進行一大堆的findviewbyid以及各種setListener的雜亂程式碼,本文以findviewbyid和setOnClickListener為例 介紹如何進行屬性繫結和事件繫結

開始實現

1.建立測試module

public class ActivityIocTest extends AppCompatActivity {
    @ViewById(R.id.tv)
    TextView mTextView;
    @ViewById(R.id.btn)
    Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ioc_test);
        ViewUtils.injectActivity(this);
        mTextView.setText("Tv text!!!!");
        mButton.setText("Button text!!!!");
    }

    @OnClick({R.id.tv,R.id.btn})
    public void onItemClick(View view){
        switch (view.getId()){
            case R.id.tv:
                Toast.makeText(this,"text view clicked",Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn:
                Toast.makeText(this,"button clicked",Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

<?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=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Ioc Test" />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Ioc Test" />

</LinearLayout>

2.建立ioc module 專門生成註解,進行屬性和事件繫結
3.測試module依賴ioc模組

dependencies {
    ...
    implementation project(path: ':ioc')//新增ioc模組註解依賴
}

4.編寫ioc模組
4.1 註解的宣告

/**
 * Created by hjcai on 2021/2/7.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    //可以傳入多個引數
    int[] value();
}
/**
 * Created by hjcai on 2021/2/5.
 * 註解宣告
 */

@Target(ElementType.FIELD)//該註解表示只能用於類的屬性
//其他常見屬性如 TYPE用在類上  CONSTRUCTOR用在建構函式上 METHOD用於方法
@Retention(RetentionPolicy.RUNTIME)
public//該註解表示在執行時生效
//其他常見屬性如 CLASS編譯時   RUNTIME執行時  SOURCE原始碼級別

@interface ViewById {
    //value代表可以該註解可以新增一個引數
    int value();
}

4.2 工具類編寫

/**
 * Created by hjcai on 2021/2/5.
 * <p>
 * 主要是呼叫findViewById
 */
class ViewFinder {
    private Activity mActivity;

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

    public View findViewById(int viewId) {
        return mActivity.findViewById(viewId);
    }
}

4.3 實際注入屬性與方法的類

/**
 * Created by hjcai on 2021/2/5.
 */
public class ViewUtils {

    //Activity繫結
    public static void injectActivity(Activity activity) {
        injectActivity(new ViewFinder(activity), activity);
    }

    private static void injectActivity(ViewFinder finder, Object object) {
        injectActivityField(finder, object);
        injectActivityEvent(finder, object);
    }

    //注入屬性
    private static void injectActivityField(ViewFinder finder, Object object) {
        //1 反射 獲取class裡面所有屬性
        Class<?> clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();//獲取Activity的所有屬性包括私有和共有
        for (Field field : fields) {
            //2 遍歷fields 找到添加了註解ViewById的filed
            ViewById viewById = field.getAnnotation(ViewById.class);
            if (viewById != null) {
                //3 獲取註解裡面的id值
                int viewId = viewById.value();
                View view = finder.findViewById(viewId);//相當於呼叫Activity.findViewById
                if (view != null) {
                    field.setAccessible(true);
                    try {
                        //4 動態的注入找到的View
                        field.set(object, view);//利用反射 將object(activity)中的聲明瞭ViewById註解的地方 替換成剛剛find的view
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private static void injectActivityEvent(ViewFinder finder, Object object) {
        //1 反射 獲取class裡面所有屬性
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();//獲取Activity的所有方法包括私有和共有
        for (Method method : methods) {
            //2 遍歷fields 找到添加了註解OnClick的method
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null) {
                //3 獲取註解裡面的id值
                int[] viewIds = onClick.value();
                for (int viewId : viewIds) {
                    View view = finder.findViewById(viewId);//相當於呼叫Activity.findViewById
                    if (view != null) {
                        // 4. 給每個view設定點選事件
                        view.setOnClickListener(new DeclaredOnClickListener(method, object));
                    }
                }
            }
        }
    }

    private static class DeclaredOnClickListener implements View.OnClickListener {
        private Object mObject;
        private Method mMethod;//實際定義的方法 在這裡是 ActivityIocTest.onItemClick

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

        @Override
        public void onClick(View v) {
            try {
                // 所有方法都可以 包括私有共有
                mMethod.setAccessible(true);
                // 反射執行方法
                // 當View被點選時執行
                mMethod.invoke(mObject, v);//通過反射 呼叫mObject的聲明瞭onClick註解了的方法 引數為v
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mObject, null);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
}

小結

這裡的原理和xUtil3裡面的ioc註解原理一樣都是利用annotation+反射實現的