Android進階:框架打造之IOC框架

什麼是IOC

IOC(Inversion of Control):控制反轉。開發過程當中類裏面須要用到不少個成員變量java

傳統的寫法:你要用這些成員變量的時候,那麼你就new出來用 IOC的寫法:你要用這些成員變量的時候,使用註解的方式自動注入進去 優勢:代碼量減小,加速開發 缺點:性能消耗加大,閱讀性差,加速65535數組

框架的思路

框架例子bash

//實現Button自動findViewById的工做
@ViewById(R.id.bt_ioc)
private Button bt_ioc;
複製代碼

實現思路網絡

  • 建立自定義註解 @ViewById
  • 經過某個字節碼文件獲取對應的自定義註解
  • 經過反射,獲取註解和註解的值 (R.id.bt_ioc)
  • 經過對註解的值作相應的操做,並設置回對象自身

實現內容app

  • 實現經過Id找到控件的功能
  • 實現經過Id找到Color、String資源
  • 實現綁定view的點擊事件、長按事件
  • 實現綁定SetContentView
  • 實現綁定網絡的檢測功能

框架的結構

包含的註解介紹框架

  • OnClick:實現點擊事件
  • OnLongClick:實現長按事件
  • ColorById:找到對應的Color值
  • ContentViewById:實現SetContentView
  • StringById:找到對應的String值
  • ViewById:實現findViewById
  • CheckNet:實現網絡檢查功能

框架的使用

下面的這個Activity實現了框架的全部內容ide

@ContentViewById(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.bt_ioc)
    private Button bt_ioc;
    @StringById(R.string.app_name)
    private String app_name;
    @ColorById(R.color.colorAccent)
    private int color;

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

        //IOC演示
        InjectManager.inject(this);

        bt_ioc.setText(app_name);
        bt_ioc.setBackgroundColor(color);
    }

    //支持數組形式的綁定,綁定多個控件
    @OnClick({R.id.open_ioc})
    @OnLongClick({R.id.open_ioc})
    @CheckNet()
    public void open_ioc() {
        Toast.makeText(this, "網絡可用", Toast.LENGTH_SHORT).show();
    }
}
複製代碼

框架的實現

框架的實現分爲兩步:自定義註解的建立和經過反射進行注入函數

1、自定義註解

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

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

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

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

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

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNet {

}
複製代碼

Target註解的介紹佈局

  • @Target(ElementType.XXX):表明的是註解放在XXX位置
  • @Target(ElementType.TYPE):接口、類、枚舉、註解
  • @Target(ElementType.FIELD):字段、枚舉的常量
  • @Target(ElementType.METHOD):方法
  • @Target(ElementType.PARAMETER):方法參數
  • @Target(ElementType.CONSTRUCTOR):構造函數
  • @Target(ElementType.LOCAL_VARIABLE):局部變量
  • @Target(ElementType.ANNOTATION_TYPE):註解
  • @Target(ElementType.PACKAGE):包

Retention註解的介紹性能

  • @Retention(Policy.RUNTIME):表明運行時檢測,class文件中存在
  • @Retention(Policy.CLASS):表明編譯時檢測,存在於class文件中,運行時沒法獲取
  • @Retention(Policy.SOURCE):表明在源文件中有效(在.java文件中有效)

2、注入步驟

從使用中能夠看到,注入中最重要的步驟的是:InjectManager.inject(this),這裏主要負責的事情有

  • 注入ContentView
  • 注入變量
  • 注入事件
public class InjectManager {

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

    public static void inject(Fragment fragment) {
        inject(new ViewManager(fragment), fragment);
    }

    /**
     * 注入
     *
     * @param viewManager
     * @param object
     */
    private static void inject(ViewManager viewManager, Object object) {
        InjectManagerService.injectContentView(viewManager, object);
        InjectManagerService.injectField(viewManager, object);
        InjectManagerService.injectEvent(viewManager, object);
    }
}
複製代碼

這裏會使用到ViewManager輔助類,代碼很簡單,後面會用到

public class ViewManager {

    private Activity mActivity;
    private Fragment mFragment;
    private View mView;

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

    public ViewManager(View view) {
        this.mView = view;
    }

    public ViewManager(Fragment fragment) {
        this.mFragment = fragment;
    }

    /**
     * 經過Id查詢View
     *
     * @param resId
     * @return
     */
    public View findViewById(int resId) {
        View view = null;
        if (mActivity != null) {
            view = mActivity.findViewById(resId);
        }
        if (mFragment != null) {
            view = mFragment.getActivity().findViewById(resId);
        }
        if (mView != null) {
            view = mView.findViewById(resId);
        }
        return view;
    }

    /**
     * 設置根佈局,僅限Activity
     *
     * @param resId
     */
    public void setContentView(int resId) {
        if (mActivity != null) {
            mActivity.setContentView(resId);
        }
    }

    /**
     * 獲取顏色
     *
     * @param resId
     */
    public int getColor(int resId) {
        int color = -1;
        if (mActivity != null) {
            color = mActivity.getResources().getColor(resId);
        }
        if (mFragment != null) {
            color = mFragment.getActivity().getResources().getColor(resId);
        }
        return color;
    }

    /**
     * 獲取字符串
     *
     * @param resId
     */
    public String getString(int resId) {
        String str = "";
        if (mActivity != null) {
            str = mActivity.getString(resId);
        }
        if (mFragment != null) {
            str = mFragment.getActivity().getString(resId);
        }
        return str;
    }
}
複製代碼

在InjectManagerService中,也是上面的三個主要步驟,主要仍是下面經過反射實現其真正的效果

public class InjectManagerService {

    /**
     * 注入根佈局
     *
     * @param viewManager
     * @param object
     */
    public static void injectContentView(ViewManager viewManager, Object object) {
        injectContentViewById(viewManager, object);
    }

    /**
     * 注入變量
     *
     * @param viewManager
     * @param object
     */
    public static void injectField(ViewManager viewManager, Object object) {
        injectFieldById(viewManager, object);
    }

    /**
     * 注入事件
     *
     * @param viewManager
     * @param object
     */
    public static void injectEvent(ViewManager viewManager, Object object) {
        injectOnClick(viewManager, object);
        injectOnLongClick(viewManager, object);
    }

    /**
     * 注入根佈局
     *
     * @param viewManager
     * @param object
     */
    private static void injectContentViewById(ViewManager viewManager, Object object) {
        Class<?> clazz = object.getClass();
        ContentViewById contentView = clazz.getAnnotation(ContentViewById.class);
        if (contentView != null) {
            int layoutId = contentView.value();
            viewManager.setContentView(layoutId);
        }
    }

    /**
     * 注入findViewById事件
     *
     * @param viewManager
     * @param object
     */
    public static void injectFieldById(ViewManager viewManager, Object object) {
        //1. 獲取Activity字節碼,這裏以Activity爲例
        Class<?> clazz = object.getClass();
        //2. 獲取字節碼中全部的成員變量
        Field[] fields = clazz.getDeclaredFields();
        if (fields != null) {
            //3. 遍歷全部變量
            for (Field field : fields) {
                //4. 找到對應的註解
                ViewById viewById = field.getAnnotation(ViewById.class);
                StringById stringById = field.getAnnotation(StringById.class);
                ColorById colorById = field.getAnnotation(ColorById.class);

                if (viewById != null) {
                    //5. 獲取註解中的值
                    int viewId = viewById.value();
                    //6. findViewById並設置訪問權限
                    View view = viewManager.findViewById(viewId);
                    field.setAccessible(true);
                    try {
                        //7. 動態注入到變量中
                        field.set(object, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }

                if (stringById != null) {
                    int viewId = stringById.value();
                    String string = viewManager.getString(viewId);
                    field.setAccessible(true);
                    try {
                        field.set(object, string);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }

                if (colorById != null) {
                    int viewId = colorById.value();
                    int color = viewManager.getColor(viewId);
                    field.setAccessible(true);
                    try {
                        field.set(object, color);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
    }

    /**
     * 注入點擊事件
     *
     * @param viewManager
     * @param object
     */
    public static void injectOnClick(ViewManager viewManager, Object object) {
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        if (methods != null) {
            for (Method method : methods) {
                OnClick onClick = method.getAnnotation(OnClick.class);
                if (onClick != null) {
                    int[] viewIds = onClick.value();
                    for (int viewId : viewIds) {
                        View view = viewManager.findViewById(viewId);
                        //檢查網絡
                        boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
                        if (view != null) {
                            view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet));
                        }
                    }
                }
            }
        }
    }

    /**
     * 注入長按事件
     *
     * @param viewManager
     * @param object
     */
    public static void injectOnLongClick(ViewManager viewManager, Object object) {
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        if (methods != null) {
            for (Method method : methods) {
                OnLongClick onLongClick = method.getAnnotation(OnLongClick.class);
                if (onLongClick != null) {
                    int[] viewIds = onLongClick.value();
                    for (int viewId : viewIds) {
                        View view = viewManager.findViewById(viewId);
                        //檢查網絡
                        boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
                        if (view != null) {
                            view.setOnLongClickListener(new DeclaredOnLongClickListener(method, object, isCheckNet));
                        }
                    }
                }
            }
        }
    }
}
複製代碼

這裏用到兩個點擊事件,而且將檢查網絡做爲參數傳進去到事件中處理,因爲長按事件和點擊事件大同小異,這裏只貼一處代碼

public class DeclaredOnLongClickListener implements View.OnLongClickListener {

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

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

    @Override
    public boolean onLongClick(View v) {
        if (mIsCheckNet) {
            if (!NetUtils.isNetworkAvailable(v.getContext())) {
                Toast.makeText(v.getContext(), "網絡不可用", Toast.LENGTH_SHORT).show();
                return true;
            }
        }
        //執行點擊事件
        try {
            mMethod.setAccessible(true);
            mMethod.invoke(mObject, v);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                mMethod.invoke(mObject, null);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
        return true;
    }
}
複製代碼

結語

到這裏IOC框架就結束了,其中比較重要的兩點是註解的自定義和經過反射獲取屬性值並注入,其實代碼挺簡單的,反覆看看仍是挺容易理解的,你們能夠結合源碼進行閱讀,其實在IOC路上還有權限的申請等功能能夠實現,不過已經有第三方框架已經作好了.

相關文章
相關標籤/搜索