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(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都可以很方便的集成上述框架,而且资源包很小,具体方法大家网上找一找,我就不再列出具体步骤了。
既然此类框架如此炫酷高效,那么我们是否应该大肆推崇,广泛采用呢。这个具体还是根据自己的项目实际情况来定吧,据我小范围了解,目前部分大厂出品的部分应用并未采用此类框架,最后我们还是来看看此类框架的优缺点吧。
优点
1:提高开发效率
2:减少代码量
缺点
1:代码可读性差
2:增加新人学习成本
3:加速触及65535方法数问
你用或者不用,框架就在那里,不悲不喜!