1.概述
這是我們的內(nèi)涵段子系統(tǒng)架構(gòu)的第一期分享都伪,希望大家可以先去了解一下這一期的內(nèi)容:2017Android進(jìn)階之路與你同行呕乎。在介紹內(nèi)涵段子整個(gè)項(xiàng)目的時(shí)候我們也說(shuō)好了會(huì)分析系統(tǒng)源碼設(shè)計(jì)模式,第三方框架源碼解析陨晶,然后自己動(dòng)手一點(diǎn)一點(diǎn)打造一套內(nèi)涵段子框架楣嘁。這一期的內(nèi)容對(duì)于部分哥們可能有點(diǎn)麻煩,如果覺(jué)得抽象請(qǐng)看視頻講解珍逸。
那么什么是IOC逐虚,控制反轉(zhuǎn)(Inversion of Control,英文縮寫(xiě)為IOC)谆膳,其實(shí)就是反射加注解如果你學(xué)過(guò)Java后臺(tái)這個(gè)在三大框架中會(huì)經(jīng)常使用叭爱。過(guò)多的去解釋其實(shí)也沒(méi)什么意思,我們主要來(lái)看有什么用處漱病。
附視頻講解地址:http://pan.baidu.com/s/1kVFMRQJ
2.第三方IOC框架源碼解析
今天主要講的就是Android中IOC框架就是注入控件和布局或者說(shuō)是設(shè)置點(diǎn)擊監(jiān)聽(tīng)买雾,如果你用過(guò)xUtils,afinal杨帽,butterknife類(lèi)的框架漓穿,你肯定不陌生~
我們挑兩個(gè)做一下對(duì)比和源碼分析,我們就挑xUtils和butterknife這兩個(gè)代表:
2.1 xUtils的IOC注解使用
xutils如果大家使用過(guò)的話它里面的內(nèi)容會(huì)比較多注盈,包括網(wǎng)絡(luò)晃危,數(shù)據(jù)庫(kù),IOC注入,網(wǎng)絡(luò)圖片使用僚饭,那么我們這里主要看看xutils3.0的IOC注解:https://github.com/wyouflf/xUtils3
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.icon)
private ImageView mIconIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
x.view().inject(this);
mIconIv.setImageResource(R.drawable.icon);
}
/**
* 1. 方法必須私有限定,
* 2. 方法參數(shù)形式必須和type對(duì)應(yīng)的Listener接口一致.
* 3. 注解參數(shù)value支持?jǐn)?shù)組: value={id1, id2, id3}
* 4. 其它參數(shù)說(shuō)明見(jiàn){@link org.xutils.view.annotation.Event}類(lèi)的說(shuō)明.
**/
@Event(value = R.id.icon,
type = View.OnClickListener.class/*可選參數(shù), 默認(rèn)是View.OnClickListener.class*/)
private void iconIvClick(View view) {
Toast.makeText(this, "圖片被點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
}
我就是設(shè)置一張圖片和一個(gè)點(diǎn)擊事件而已震叮,其實(shí)主要解決的就是我們不再需要findViewById()和setOnClickListener(),我們簡(jiǎn)單的來(lái)看一下源碼到底是怎么實(shí)現(xiàn)的:
2.2 xUtils的IOC注解源碼解析
我就挑一些關(guān)鍵的代碼來(lái)分析一下鳍鸵,視頻里面會(huì)給大家講得非常詳細(xì)苇瓣,我們主要看一下x.view().inject(this);到底是干了什么:
@Override
public void inject(Activity activity) {
//獲取Activity的ContentView的注解
Class<?> handlerType = activity.getClass();
try {
// 找到ContentView這個(gè)注解,在activity類(lèi)上面獲取
ContentView contentView = findContentView(handlerType);
if (contentView != null) {
int viewId = contentView.value();
if (viewId > 0) {
// 如果有注解獲取layoutId的值偿乖,利用反射調(diào)用activity的setContentView方法注入視圖
Method setContentViewMethod =
handlerType.getMethod("setContentView", int.class);
setContentViewMethod.invoke(activity, viewId);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
// 處理 findViewById和setOnclickListener的注解
injectObject(activity, handlerType, new ViewFinder(activity));
}
private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
// .......
// 從父類(lèi)到子類(lèi)遞歸
injectObject(handler, handlerType.getSuperclass(), finder);
// inject view 注入控件View
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
// ......
// 獲取viewInject 注解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
// 其實(shí)最終還是調(diào)用findViewById的方法
View view = finder.findViewById(viewInject.value(),
viewInject.parentId());
if (view != null) {
// 利用反射 把View注入到該屬性中
field.setAccessible(true);
field.set(handler, view);
} else {
// ......
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject view
// inject event 注入事件
Method[] methods = handlerType.getDeclaredMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
// ......
//檢查當(dāng)前方法是否是event注解的方法
Event event = method.getAnnotation(Event.class);
if (event != null) {
try {
// id參數(shù)
int[] values = event.value();
int[] parentIds = event.parentId();
int parentIdsLen = parentIds == null ? 0 : parentIds.length;
//循環(huán)所有id击罪,生成ViewInfo并添加代理反射 主要使用了動(dòng)態(tài)代理的設(shè)計(jì)模式
for (int i = 0; i < values.length; i++) {
int value = values[i];
if (value > 0) {
ViewInfo info = new ViewInfo();
info.value = value;
info.parentId = parentIdsLen > i ? parentIds[i] : 0;
method.setAccessible(true);
// EventListenerManager 動(dòng)態(tài)代理執(zhí)行相應(yīng)的方法
EventListenerManager.addEventMethod(
finder, info, event, handler, method);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject event
}
關(guān)鍵的源碼大概就這么多,動(dòng)態(tài)代理的部分沒(méi)有貼出來(lái)贪薪,這個(gè)寫(xiě)可能寫(xiě)不清楚大家可以自己去看看源碼或者自己去網(wǎng)上搜搜動(dòng)態(tài)代理的設(shè)計(jì)模式分析外邓,視頻里面會(huì)給大家講清楚。動(dòng)態(tài)代理我記得我剛剛自學(xué)那會(huì)還真是一道坎但是這個(gè)坎我們得邁過(guò)去古掏,后面我們講Android的Hook技術(shù)以及插件開(kāi)發(fā)會(huì)反復(fù)的講到损话。
xutils相對(duì)來(lái)說(shuō)應(yīng)該不難理解吧,其實(shí)就是我們利用類(lèi)的反射循環(huán)獲取屬性的注解值然后通過(guò)findViewById之后槽唾,動(dòng)態(tài)的注入到控件屬性里面丧枪;事件注入也是類(lèi)似首先f(wàn)indViewById然后利用動(dòng)態(tài)代理去反射執(zhí)行方法。
2.3 butterknife的使用
相比于xutils來(lái)講它可能更受大家的歡迎庞萍,第一在性能方面xutils完全是利用的反射拧烦,butterknife是輕量級(jí)的反射使用的注解都是編譯時(shí)注解,而且它還提供了一個(gè)Android Studio的插件不需要我們?nèi)?xiě)任何的代碼钝计,作者JakeWharton很出名的寫(xiě)過(guò)很多大型的第三方框架恋博,https://github.com/JakeWharton/butterknife
public class MainActivity extends AppCompatActivity {
@Bind(R.id.icon)
ImageView icon;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.icon)
public void onClick() {
Toast.makeText(this, "圖片點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
}
上面是由插件給我們自動(dòng)生成的代碼,我們?cè)僖膊挥檬謩?dòng)去寫(xiě)這些代碼了私恬。屬性是不能private,onClick()方法也不能private,否則會(huì)報(bào)錯(cuò)的待會(huì)我們看源碼的實(shí)現(xiàn)就知道為什么只能這樣债沮,插件自動(dòng)生成的名字和方法總感覺(jué)怪怪的和我們的Android源碼規(guī)范不一致,當(dāng)然后面我們會(huì)自己去寫(xiě)一個(gè)Android Studio插件來(lái)配合我們自己的IOC注解框架本鸣。
2.4 butterknife的源碼解析
源碼閱讀相對(duì)于xutils的也麻煩很多疫衩,如果我們按照常規(guī)的邏輯去找ButterKnife.bind(this);會(huì)發(fā)現(xiàn)里面好像沒(méi)什么東西只有這個(gè):
static void bind(Object target, Object source, Finder finder) {
Class<?> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
// 找到viewBinder
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
if (viewBinder != null) {
// 直接執(zhí)行方法
viewBinder.bind(finder, target, source);
}
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " +
targetClass.getName(), e);
}
}
如果從這里看我們好像看不到任何的東西,其實(shí)工作流程是怎樣的呢荣德?我們可以看一下Bind這個(gè)Annotation注解類(lèi)和ButterKnifeProcessor這兩個(gè)類(lèi)其實(shí)就能找到線索:
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
int[] value();
}
@Retention 為 CLASS 的 Annotation闷煤,由apt(Annotation Processing Tool) 解析自動(dòng)解析。ButterKnife便是用了Java Annotation Processing技術(shù)涮瞻,就是在Java代碼編譯成Java字節(jié)碼的時(shí)候就已經(jīng)處理了@Bind鲤拿、@OnClick(ButterKnife還支持很多其他的注解)這些注解了。
你可以你定義注解署咽,并且自己定義解析器來(lái)處理它們近顷。Annotation processing是在編譯階段執(zhí)行的,它的原理就是讀入Java源代碼,解析注解幕庐,然后生成新的Java代碼。新生成的Java代碼最后被編譯成Java字節(jié)碼家淤,注解解析器(Annotation Processor)不能改變讀入的Java 類(lèi)异剥,比如不能加入或刪除Java方法。下面我們應(yīng)該就大概知道工作流程了吧絮重?
2.5 ButterKnife 工作流程
當(dāng)你編譯你的Android工程時(shí)冤寿,ButterKnife工程中ButterKnifeProcessor類(lèi)的process()方法會(huì)執(zhí)行以下操作:
開(kāi)始它會(huì)掃描Java代碼中所有的ButterKnife注解@Bind、@OnClick青伤、@OnItemClicked等督怜。
當(dāng)它發(fā)現(xiàn)一個(gè)類(lèi)中含有任何一個(gè)注解時(shí),ButterKnifeProcessor會(huì)幫你生成一個(gè)Java類(lèi)狠角,名字類(lèi)似$$ViewBinder号杠,這個(gè)新生成的類(lèi)實(shí)現(xiàn)了ViewBinder接口。
這個(gè)ViewBinder類(lèi)中包含了所有對(duì)應(yīng)的代碼丰歌,比如@Bind注解對(duì)應(yīng)findViewById(), @OnClick對(duì)應(yīng)了view.setOnClickListener()等等姨蟋。
最后當(dāng)Activity啟動(dòng)ButterKnife.bind(this)執(zhí)行時(shí),ButterKnife會(huì)去加載對(duì)應(yīng)的ViewBinder類(lèi)調(diào)用它們的bind()方法立帖。
現(xiàn)在我們總該明白為什么我們的生成的屬性和方法不能私有了吧眼溶?我們最后看一下編譯時(shí)生成的class類(lèi)吧
public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131427372, "field 'icon' and method 'onClick'");
target.icon = finder.castView(view, 2131427372, "field 'icon'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(View p0) {
target.onClick();
}
});
}
@Override public void unbind(T target) {
target.icon = null;
}
}
如果現(xiàn)在要我們來(lái)選,我肯定會(huì)選butterknife這個(gè)主要是因?yàn)樗幸粋€(gè)插件我完全不用寫(xiě)任何的代碼晓勇,而且沒(méi)有利用不是全反射在性能方面也有一點(diǎn)提升堂飞,但是有的時(shí)候我想加一個(gè)檢測(cè)網(wǎng)絡(luò)的注解,這就有點(diǎn)麻煩了绑咱,插件生成的屬性名稱和方法名也有點(diǎn)蛋疼绰筛,我們自己來(lái)試著寫(xiě)寫(xiě)吧。
3.自己動(dòng)手豐衣足食
接下來(lái)我們自己來(lái)實(shí)現(xiàn)一套IOC注解框架吧描融,采用的方式反射加注解和Xutils類(lèi)似别智,但我們盡量不寫(xiě)那么麻煩,也不打算采用動(dòng)態(tài)代理稼稿,我們擴(kuò)展一個(gè)檢測(cè)網(wǎng)絡(luò)的注解薄榛,比如沒(méi)網(wǎng)的時(shí)候我們不去執(zhí)行方法而是給予沒(méi)有網(wǎng)絡(luò)的提示同時(shí)也不允許用戶反復(fù)點(diǎn)擊。
這個(gè)時(shí)候有人就開(kāi)始噴了让歼,明知道反射會(huì)影響性能為什么還要用敞恋?這里我就隨便說(shuō)說(shuō)吧,我承認(rèn)反射會(huì)影響性能但是問(wèn)題不大我們可以自己去測(cè)試反射1萬(wàn)次大概會(huì)怎樣谋右,如果你非得去糾結(jié)那我也沒(méi)辦法硬猫,我們還是多花時(shí)間在UI渲染和Bitmap以及Service和Handler上面吧,我還從來(lái)沒(méi)有遇到過(guò)反射調(diào)用gc或者內(nèi)存溢出的情況,而且后面講插件化開(kāi)發(fā)的時(shí)候也會(huì)用到反射那砸門(mén)就不做了啸蜜?不管了開(kāi)工坑雅。
3.1 控件屬性注入
這里我就不在介紹Annotation的使用了,如果對(duì)于這個(gè)不是特別了解的大家可以自己去查一查資料或者看一下我錄制的視頻吧衬横,先來(lái)處理控件屬性的注入裹粤,但是需要考慮各種情況:
/**
* Created by Darren on 2017/2/4.
* Email: 240336124@qq.com
* Description: IOC的View屬性注解類(lèi)
*/
// RUNTIME 運(yùn)行時(shí)檢測(cè),CLASS 編譯時(shí)butterKnife使用是這個(gè) SOURCE 源碼資源的時(shí)候
@Retention(RetentionPolicy.RUNTIME)
// FIELD 注解只能放在屬性上 METHOD 方法上 TYPE 類(lèi)上 CONSTRUCTOR 構(gòu)造方法上
@Target(ElementType.FIELD)
public @interface ViewById {
// 代表可以傳值int類(lèi)型 使用的時(shí)候:ViewById(R.id.xxx)
int value();
}
/**
* Created by Darren on 2017/2/4.
* Email: 240336124@qq.com
* Description: IOC 注入 ViewUtils
*/
public class ViewUtils {
public static void inject(Activity activity) {
inject(new ViewFinder(activity), activity);
}
// 兼容View
public static void inject(View view) {
inject(new ViewFinder(view), view);
}
// 兼容Fragment
public static void inject(View view, Object object) {
inject(new ViewFinder(view), object);
}
private static void inject(ViewFinder viewFinder, Object object) {
injectFiled(viewFinder, object);
injectEvent(viewFinder, object);
}
// 注入事件
private static void injectEvent(ViewFinder viewFinder, Object object) {
}
/**
* 注入屬性
*/
private static void injectFiled(ViewFinder viewFinder, Object object) {
// object --> activity or fragment or view 是反射的類(lèi)
// viewFinder --> 只是一個(gè)view的findViewById的輔助類(lèi)
// 1. 獲取所有的屬性
Class<?> clazz = object.getClass();
// 獲取所有屬性包括私有和公有
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 2. 獲取屬性上面ViewById的值
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null) {
// 獲取ViewById屬性上的viewId值
int viewId = viewById.value();
// 3. 通過(guò)findViewById獲取View
View view = viewFinder.findViewById(viewId);
if (view != null) {
// 4. 反射注入View屬性
// 設(shè)置所有屬性都能注入包括私有和公有
field.setAccessible(true);
try {
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ clazz.getSimpleName() + "." + field.getName());
}
}
}
}
}
3.2 點(diǎn)擊事件注入
事件的注入我們只打算setOnclickListener其他不常見(jiàn)的我們先不管蜂林,也不打算采用動(dòng)態(tài)代理的設(shè)計(jì)模式遥诉。
// 事件注入
private static void injectEvent(ViewFinder viewFinder, Object object) {
// 1.獲取所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
// 2.獲取方法上面的所有id
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
int[] viewIds = onClick.value();
if (viewIds.length > 0) {
for (int viewId : viewIds) {
// 3.遍歷所有的id 先f(wàn)indViewById然后 setOnClickListener
View view = viewFinder.findViewById(viewId);
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, object));
}
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
private Method mMethod;
private Object mHandlerType;
public DeclaredOnClickListener(Method method, Object handlerType) {
mMethod = method;
mHandlerType = handlerType;
}
@Override
public void onClick(View v) {
// 4.反射執(zhí)行方法
mMethod.setAccessible(true);
try {
mMethod.invoke(mHandlerType, v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mHandlerType, null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.icon)
private ImageView mIconIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mIconIv.setImageResource(R.drawable.icon);
}
@OnClick(R.id.icon)
private void onClick(View view) {
int i = 2 / 0;
Toast.makeText(this, "圖片點(diǎn)擊了"+i, Toast.LENGTH_LONG).show();
}
}
使用起來(lái)和xutils類(lèi)似,方法和屬性可以私有噪叙,但是有一點(diǎn)我們?cè)贠nclick點(diǎn)擊事件的方法里面無(wú)論做什么操作都是不會(huì)報(bào)錯(cuò)的矮锈,所以如果發(fā)現(xiàn)bug需要留意警告日志,這不是坑嗲嗎睁蕾?其實(shí)在我們的開(kāi)發(fā)過(guò)程給用戶或者老板玩的時(shí)候我們最怕的是閃退苞笨,現(xiàn)在我們就算有Bug也不會(huì)出現(xiàn)閃退的情況只是調(diào)試的時(shí)候需要留意警告日志還是蠻不錯(cuò)的。
3.3 擴(kuò)展動(dòng)態(tài)檢測(cè)網(wǎng)絡(luò)注解
我們最后擴(kuò)展一下加一個(gè)檢測(cè)網(wǎng)絡(luò)的注解子眶,有的時(shí)候我們?cè)邳c(diǎn)擊的方法里面需要去檢測(cè)網(wǎng)絡(luò)猫缭,比如登陸注冊(cè),我們?nèi)绻麤](méi)網(wǎng)就沒(méi)必要去調(diào)接口啟動(dòng)線程了壹店,只需要提示用戶當(dāng)前無(wú)網(wǎng)絡(luò)即可猜丹。當(dāng)然這只是一個(gè)擴(kuò)展而已。
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.icon)
private ImageView mIconIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mIconIv.setImageResource(R.drawable.icon);
}
@OnClick(R.id.icon)
@CheckNet // 檢測(cè)網(wǎng)絡(luò)
private void onClick(View view) {
Toast.makeText(this, "圖片點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
}
擴(kuò)展我們就寫(xiě)這么個(gè)例子吧硅卢,這是內(nèi)涵段子框架搭建的第一期分享射窒,希望大家可以先去了解一下我們所有的分享內(nèi)容2017Android進(jìn)階之路與你同行,
附視頻講解地址:http://pan.baidu.com/s/1kVFMRQJ