TextView mTextView;
mTextView=(TextView) findViewById(R.id.mTextView);
mTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
作為一名Android程序員,對于上面這種機械化的代碼你一定寫到想吐了,或許多數時候你只是copy ,paste,然后再改一改,完了你可能又會覺得這種代碼毫無營養,寫得實在沒勁。俗話說:“不會偷懶的程序員不是好程序員”,今天我們就來探討下如何偷懶。
到這里可能你已經知道我要說的是什么了,是的,我要說的就是Android中的IOC框架,這類框架中比較早的有:Afinal,Xutils,目前開發者中呼聲比較高的有Android Annotations,ButterKnife,Dagger,RoboGuice等。我在這里就簡單介紹下比較有代表性的Android Annotations和ButterKnife。
什么是IOC?
Inversion of Control,英文縮寫為IOC,字面翻譯:控制反轉。什么意思呢?就是一個類里面需要用到很多個成員變量,傳統的寫法,你要用這些成員變量,那么你就new 出來用唄!IOC的原則是:NO,我們不要new,這樣耦合度太高,你配置個xml文件,里面標明哪個類,里面用了哪些成員變量,等待加載這個類的時候,我幫你注入(new)進去;當然了,你又會覺得,寫個配置文件,臥槽,這多麻煩。于是乎,又出現了另一種方案,得你嫌配置文件麻煩,那用注解唄在你需要注入的成員變量上面加個注解,例如:@Inject,這樣就行了,你總不能說這么個單詞麻煩吧。當然了,有了配置文件和注解,那么怎么注入呢?其實就是把字符串類路徑變成類么,這個時候需要用到反射。
什么是反射?
首先JAVA語言并不是一種動態編程語言,而為了使語言更靈活,JAVA引入了反射機制。 JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。Java反射機制主要提供了以下功能: 在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。
什么是注解?
JAVA1.5之后引入的注解和反射,注解的實現依賴于反射。JAVA中的注解是一種繼承自接口java.lang.annotation.Annotation的特殊接口。那么接口怎么能夠設置屬性呢?簡單來說就是JAVA通過動態代理的方式為你生成了一個實現了接口Annotation的實例,然后對該代理實例的屬性賦值,這樣就可以在程序運行時(如果將注解設置為運行時可見的話)通過反射獲取到注解的配置信息。說的通俗一點,注解相當于一種標記,在程序中加了注解就等于為程序打上了某種標記。程序可以利用JAVA的反射機制來了解你的類及各種元素上有無何種標記,針對不同的標記,就去做相應的事件。標記可以加在包,類,方法,方法的參數以及成員變量上。
以上算是背景介紹吧,也正是基于以上需求和實現原理,一大波Android IOC框架應運而生。我們先來看第一個:Android Annotations。我們先比較下常規寫法和Annotations寫法的代碼。
常規寫法
先隨便來一段吧~~
public class MainActivity extends Activity {
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text);
textView.setText("test");
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent(this, ChildActivity.class);
startActivity(intent);
}
});
}
}
Android Annotations寫法
@EActivity(R.layout.activity_main)
public class MainActivity extends Activity {
@ViewById(R.id.text)
TextView textView;
@AfterViews
public void init() {
textView.setText("annotations test");
}
@Click(R.id.text)
void buttonClick() {
Intent intent = new Intent(this,ChildActivity_.class);
startActivity(intent);
}
}
就是這么簡單
Android Annotations就這么寫?可能你會問,oncreat()去哪了,這可是執行入口,你又會擔心setContentView()呢,布局怎么加載?看上面的代碼第一行@EActivity(R.layout.activity_main),就這一句全搞定,同時也不需要搞一大堆findViewById,不需要搞一大推setOnClickListener。Android Annotations能干的事可遠不止這些,Http請求,開啟線程,事件綁定等等都可以通過一個注解標記搞定。
其他語法舉例
色值 @ColorRes
@ColorRes(R.color.backgroundColor)
int someColor;
@ColorRes
int backgroundColor;
動畫 @AnimationRes
@AnimationRes(R.anim.fadein)
XmlResourceParser xmlResAnim;
@AnimationRes
Animation fadein;
自定義View @EViewGroup
@EViewGroup(R.layout.title_with_subtitle)
public class TitleWithSubtitle extends RelativeLayout {
@ViewById
protected TextView title, subtitle;
public TitleWithSubtitle(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setTexts(String titleText, String subTitleText) {
title.setText(titleText);
subtitle.setText(subTitleText);
}
}
HttpClient @HttpsClient
@HttpsClient
HttpClient httpsClient;
UiThread @UiThread
void myMethod() {
doInUiThread("hello", 100);
}
@UiThread
void doInUiThread(String aParam, long anotherParam) {
[...]
}
怎么實現的?
Android Annotations的實現原理其實很簡單。它會使用標準的Java注解處理工具自動添加一個額外的編譯步驟生成的源代碼。其實就是生成一個原有類的子類,這個子類才是真正運行用的類。例如上面代碼使用@EActivity注解的MainActivity,將生成這個MainActivity的一個子類,它的名字是“MainActivity_”。該子類重載一些方法(例如onCreate()),通過委托方式調用了activity的相關方法。所以這里有個大坑,所有Activity的相關操作都要操作其子類,例如 AndroidManifest.xml中類名要寫成android:name=".MainActivity_",開啟MainActivity要寫成 startActivity(new Intent(this, MainActivity_.class));下面是上文中AndroidAnnotations方式寫法的MainActivity的子類MainActivity_,我貼出來大家隨便感受下。
public final class MainActivity_
extends MainActivity
implements HasViews, OnViewChangedListener
{
private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();
@Override
public void onCreate(Bundle savedInstanceState) {
OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
init_(savedInstanceState);
super.onCreate(savedInstanceState);
OnViewChangedNotifier.replaceNotifier(previousNotifier);
setContentView(layout.activity_main);
}
private void init_(Bundle savedInstanceState) {
OnViewChangedNotifier.registerOnViewChangedListener(this);
}
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
onViewChangedNotifier_.notifyViewChanged(this);
}
@Override
public void setContentView(View view, LayoutParams params) {
super.setContentView(view, params);
onViewChangedNotifier_.notifyViewChanged(this);
}
@Override
public void setContentView(View view) {
super.setContentView(view);
onViewChangedNotifier_.notifyViewChanged(this);
}
public static MainActivity_.IntentBuilder_ intent(Context context) {
return new MainActivity_.IntentBuilder_(context);
}
public static MainActivity_.IntentBuilder_ intent(Fragment supportFragment) {
return new MainActivity_.IntentBuilder_(supportFragment);
}
@Override
public void onViewChanged(HasViews hasViews) {
textView = ((TextView) hasViews.findViewById(id.text));
if (textView!= null) {
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
MainActivity_.this.buttonClick();
}
}
);
}
init();
}
public static class IntentBuilder_
extends ActivityIntentBuilder<MainActivity_.IntentBuilder_>
{
private Fragment fragmentSupport_;
public IntentBuilder_(Context context) {
super(context, MainActivity_.class);
}
public IntentBuilder_(Fragment fragment) {
super(fragment.getActivity(), MainActivity_.class);
fragmentSupport_ = fragment;
}
@Override
public void startForResult(int requestCode) {
if (fragmentSupport_!= null) {
fragmentSupport_.startActivityForResult(intent, requestCode);
} else {
if (context instanceof Activity) {
Activity activity = ((Activity) context);
ActivityCompat.startActivityForResult(activity, intent, requestCode, lastOptions);
} else {
context.startActivity(intent);
}
}
}
}
}
ButterKnife
再說說ButterKnife吧,其實原理和Android Annotations差不多,只是一些寫法和細節上有略微差別,最主要一點是沒有Android Annotations的坑,Android Studio上有個ButterKnife的插件,這個插件提供的炫酷技能幾乎是一鍵搞定findViewById和setOnClickListener,基于此ButterKnife被很多開發者所推崇,我們還是簡單對比下代碼吧。
常規寫法
public class MainActivity extends Activity {
TextView textView;
ListView listView;
ListViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text);
listView = (ListView) findViewById(R.id.ListView);
textView.setText("test");
adapter = new ListViewAdapter(MainActivity.this);
listView.setAdapter(adapter);
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
text.setText("你點擊了按鈕");
}
});
}
class ListViewAdapter extends BaseAdapter {
private Context mContext;
public ListViewAdapter(Context context) {
mContext = context;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return 1000;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
LayoutInflater layoutInflater = ((Activity) mContext)
.getLayoutInflater();
convertView = layoutInflater.inflate(R.layout.list_item,
parent, false);
holder.imageview = (ImageView) convertView
.findViewById(R.id.headshow);
holder.textview0 = (TextView) convertView
.findViewById(R.id.name);
holder.textview1 = (TextView) convertView
.findViewById(R.id.text);
convertView.setTag(convertView);
} else {
convertView = (View) convertView.getTag();
}
holder.textview0.setText("star");
holder.textview1.setText("test");
return convertView;
}
}
static class ViewHolder {
ImageView imageview;
TextView textview0;
TextView textview1;
}
}
ButterKnife寫法
public class MainActivity extends Activity {
@InjectView(R.id.text)
TextView text;
@InjectView(R.id.ListView)
ListView listView;
ListViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
adapter = new ListViewAdapter(MainActivity.this);
listView.setAdapter(adapter);
text.setText("ButterKnife test");
}
@OnClick(R.id.text)
void onClick() {
text.setText("你點擊了按鈕");
}
class ListViewAdapter extends BaseAdapter {
private Context mContext;
public ListViewAdapter(Context context) {
mContext = context;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return 1000;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater layoutInflater = ((Activity) mContext)
.getLayoutInflater();
convertView = layoutInflater.inflate(
R.layout.list_item, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(convertView);
} else {
convertView = (View) convertView.getTag();
}
holder.textview0.setText("butterknife");
holder.textview1.setText("test");
return convertView;
}
}
static class ViewHolder {
@InjectView(R.id.headshow)
ImageView imageview;
@InjectView(R.id.name)
TextView textview0;
@InjectView(R.id.text)
TextView textview1;
public ViewHolder(View view) {
ButterKnife.inject(this, view);
}
}
}
整體寫法與Android Annotations類似,實現原理上ButterKnife的實現也是在編譯的過程中生成了另外一個類來幫我們完成一些基本操作,以上面的代碼為例,生成了一個名為MainActivity$$ViewInjector的類,這里我就不再貼代碼了。但與Android Annotations所不同的是,我們在代碼中操作的還是MainActivity,而并不是MainActivity$$ViewInjector。
ButterKnife其他語法列舉
資源注入
class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red;
@BindDimen(R.dimen.spacer) Float spacer;
// ...
}
Fragment注入
public class FancyFragment extends Fragment {
@InjectView(R.id.button1) Button button1;
@InjectView(R.id.button2) Button button2;
@Override View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
ButterKnife.inject(this, view);
// TODO Use "injected" views...
return view;
}
}
回調函數注入
// 帶有 Button 參數
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
// 不帶參數
@OnClick(R.id.submit)
public void submit() {
// TODO submit data to server...
}
// 同時注入多個 View 事件
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
if (door.hasPrizeBehind()) {
Toast.makeText(this, "You win!", LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Try again", LENGTH_SHORT).show();
}
}
性能如何
關于IOC框架的基本寫法和實現原理,通過上面兩個例子,相信大家都已經有所了解。但是前面已經說了IOC的注解機制是依賴JAVA的反射,可能很多開發者都會嗤之以鼻:反射會影響性能。在早期的JAVA語言中反射是會帶來不小的性能消耗,而隨著語言自身的進步和完善,到了現在情況有所好轉。但我們移動設備的性能,不比后臺服務器擁有充足的內存和運算能力。當大量的使用注解的時候,會不會對APP造成什么不良的影響,會不會影響到APP的執行性能?在這里先明確的聲明,上述框架不會給APP帶來任何副作用,相反它強大易用的api能為你帶來前所未有的編程體驗。
上述框架的實現,都是通過使用jdk 1.6引入的Java Annotation Processing Tool,在編譯器中加了一層額外的自動編譯步驟,用來生成基于你源碼的代碼。運行期運行的其實就是這個二次編譯的代碼,實際上反射注解這一過程是在編譯期完成的,而并不會影響Runtime。所以上述IOC框架并不會影響到APP得性能。簡單的做了幾組測試,分別用Android Annotations,ButterKnife,以及常規寫法寫了同樣實現的代碼,然后打印代碼執行時間。每組大概跑200次,然后計算代碼執行的平均耗時,發現三組數據基本是在同一個水平上,并不能判斷孰優孰劣。
IDE集成方法
不管是Eclipse還是Android Studio都可以很方便的集成上述框架,而且資源包很小,具體方法大家網上找一找,我就不再列出具體步驟了。從網上偷了張ButterKnife的插件技能圖,大家隨便感受下。
既然此類框架如此炫酷高效,那么我們是否應該大肆推崇,廣泛采用呢。這個具體還是根據自己的項目實際情況來定吧,據我小范圍了解,目前部分大廠出品的部分應用并未采用此類框架,最后我們還是來看看此類框架的優缺點吧。
優點
1:提高開發效率
2:減少代碼量
缺點
1:代碼可讀性差
2:增加新人學習成本
3:加速觸及65535方法數問
你用或者不用,框架就在那里,不悲不喜!
(如有刊物,歡迎指正)