Android進階——框架之IOC框架

什么是IOC

IOC(Inversion of Control):控制反轉(zhuǎn)娄周。開發(fā)過程中類里面需要用到很多個成員變量

傳統(tǒng)的寫法:你要用這些成員變量的時候,那么你就new出來用
IOC的寫法:你要用這些成員變量的時候羹奉,使用注解的方式自動注入進去
優(yōu)點:代碼量減少秒旋,加速開發(fā)
缺點:性能消耗加大,閱讀性差诀拭,加速65535

框架的思路

框架例子

//實現(xiàn)Button自動findViewById的工作
@ViewById(R.id.bt_ioc)
private Button bt_ioc;1

實現(xiàn)思路
?創(chuàng)建自定義注解 @ViewById
?通過某個字節(jié)碼文件獲取對應的自定義注解
?通過反射迁筛,獲取注解和注解的值 (R.id.bt_ioc)
?通過對注解的值做相應的操作,并設置回對象自身

實現(xiàn)內(nèi)容
?實現(xiàn)通過Id找到控件的功能
?實現(xiàn)通過Id找到Color耕挨、String資源
?實現(xiàn)綁定view的點擊事件细卧、長按事件
?實現(xiàn)綁定SetContentView
?實現(xiàn)綁定網(wǎng)絡的檢測功能

框架的結(jié)構(gòu)
20171020001616734.png

包含的注解介紹

OnClick:實現(xiàn)點擊事件
OnLongClick:實現(xiàn)長按事件
ColorById:找到對應的Color值
ContentViewById:實現(xiàn)SetContentView
StringById:找到對應的String值
ViewById:實現(xiàn)findViewById
CheckNet:實現(xiàn)網(wǎng)絡檢查功能


框架的使用

下面的這個Activity實現(xiàn)了框架的所有內(nèi)容

@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);
    }

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

框架的實現(xiàn)

框架的實現(xiàn)分為兩步:自定義注解的創(chuàng)建和通過反射進行注入

一筒占、自定義注解

@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):方法參數(shù)
@Target(ElementType.CONSTRUCTOR):構(gòu)造函數(shù)
@Target(ElementType.LOCAL_VARIABLE):局部變量
@Target(ElementType.ANNOTATION_TYPE):注解
@Target(ElementType.PACKAGE):包

Retention注解的介紹

@Retention(Policy.RUNTIME):代表運行時檢測,class文件中存在
@Retention(Policy.CLASS):代表編譯時檢測奏窑,存在于class文件中导披,運行時無法獲取
@Retention(Policy.SOURCE):代表在源文件中有效(在.java文件中有效)

二、注入步驟

從使用中可以看到埃唯,注入中最重要的步驟的是: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中,也是上面的三個主要步驟巍实,主要還是下面通過反射實現(xiàn)其真正的效果
···
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字節(jié)碼滓技,這里以Activity為例
    Class<?> clazz = object.getClass();
    //2. 獲取字節(jié)碼中所有的成員變量
    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并設置訪問權(quán)限
                View view = viewManager.findViewById(viewId);
                field.setAccessible(true);
                try {
                    //7. 動態(tài)注入到變量中
                    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);
                    //檢查網(wǎng)絡
                    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);
                    //檢查網(wǎng)絡
                    boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
                    if (view != null) {
                        view.setOnLongClickListener(new DeclaredOnLongClickListener(method, object, isCheckNet));
                    }
                }
            }
        }
    }
}

···

這里用到兩個點擊事件,并且將檢查網(wǎng)絡作為參數(shù)傳進去到事件中處理棚潦,由于長按事件和點擊事件大同小異令漂,這里只貼一處代碼
···
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(), "網(wǎng)絡不可用", Toast.LENGTH_SHORT).show();
            return true;
        }
    }
    //執(zhí)行點擊事件
    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;
}

···

結(jié)語

到這里IOC框架就結(jié)束了,其中比較重要的兩點是注解的自定義和通過反射獲取屬性值并注入丸边,其實代碼挺簡單的叠必,反復看看還是挺容易理解的,大家可以結(jié)合源碼進行閱讀妹窖,其實在IOC路上還有權(quán)限的申請等功能可以實現(xiàn)纬朝,不過已經(jīng)有第三方框架已經(jīng)做好了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市骄呼,隨后出現(xiàn)的幾起案子共苛,更是在濱河造成了極大的恐慌判没,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隅茎,死亡現(xiàn)場離奇詭異澄峰,居然都是意外死亡,警方通過查閱死者的電腦和手機辟犀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門俏竞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人堂竟,你說我怎么就攤上這事魂毁。” “怎么了出嘹?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵漱牵,是天一觀的道長。 經(jīng)常有香客問我疚漆,道長,這世上最難降的妖魔是什么刁赦? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任娶聘,我火速辦了婚禮,結(jié)果婚禮上甚脉,老公的妹妹穿的比我還像新娘丸升。我一直安慰自己,他們只是感情好牺氨,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布狡耻。 她就那樣靜靜地躺著,像睡著了一般猴凹。 火紅的嫁衣襯著肌膚如雪夷狰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天郊霎,我揣著相機與錄音沼头,去河邊找鬼。 笑死书劝,一個胖子當著我的面吹牛进倍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播购对,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼猾昆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骡苞?” 一聲冷哼從身側(cè)響起垂蜗,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤楷扬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后么抗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毅否,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年蝇刀,在試婚紗的時候發(fā)現(xiàn)自己被綠了螟加。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡吞琐,死狀恐怖捆探,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情站粟,我是刑警寧澤黍图,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布藤树,位于F島的核電站谷醉,受9級特大地震影響颤诀,放射性物質(zhì)發(fā)生泄漏斩萌。R本人自食惡果不足惜翰撑,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一养距、第九天 我趴在偏房一處隱蔽的房頂上張望和二。 院中可真熱鬧贫母,春花似錦幅虑、人聲如沸丰滑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褒墨。三九已至,卻和暖如春擎宝,著一層夾襖步出監(jiān)牢的瞬間郁妈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工认臊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留圃庭,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓失晴,卻偏偏與公主長得像剧腻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子涂屁,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容