最近开始研究快速开发框架,在网上一搜,有不少,在csdn的一篇文章上找到的github最火的二十个开源工程中找到了Android Annotations(简称AA),采用的是注解的方式,其优点有很多,例如使用注解来注入view可以减少最少三分之一的代码量,把我们从大量的findViewById中解救出来,但是我看中的比别的注解框架优秀的地方在于,与别的注解框架在程序运行时才进行遍历和反射不同,AA在编译阶段就进行了这一过程,因此不会造成性能下降,而且在对6.0的兼容方面据说做的比xUtils好多了,所以闲话少说,赶紧开始。
环境配置
我用的是Android Studio1.5来学习AA(版本4.0.0),对于AS来说配置框架是比较简单的,按照官网的指导,首先我们需要在工程下找到build.gradle文件,对比下面的代码进行修改。
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:1.5.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'//增加 } } repositories {<span style="font-family: Arial, Helvetica, sans-serif;">//增加</span> mavenCentral() mavenLocal() } apply plugin: 'android'//增加 apply plugin: 'android-apt'//增加 def AAVersion = '4.0.0'//增加,决定AA的版本号 dependencies { compile fileTree(dir: 'libs', include: '*.jar') compile files('libs/volley.jar') compile files('libs/android-support-v7-recyclerview.jar') compile files('libs/android-support-v4.jar') compile files('libs/litepal-1.1.1.jar') apt "org.androidannotations:androidannotations:$AAVersion"//增加 compile "org.androidannotations:androidannotations-api:$AAVersion"//增加 } apt {//增加 arguments { androidManifestFile variant.outputs[0]?.processResources?.manifestFile // if you have multiple outputs (when using splits), you may want to have other index than 0 // you should set your package name here if you are using different application IDs // resourcePackageName "your.package.name" // You can set optional annotation processing options here, like these commented options: // logLevel 'INFO' // logFile '/var/log/aa.log' } } android { compileSdkVersion 19 buildToolsVersion "21.1.2" sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } // Move the tests to tests/java, tests/res, etc... instrumentTest.setRoot('tests') // Move the build types to build-types/<type> // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... // This moves them out of them default location under src/<type>/... which would // conflict with src/ being used by the main source set. // Adding new build types or product flavors should be accompanied // by a similar customization. debug.setRoot('build-types/debug') release.setRoot('build-types/release') } }
配置完成后,我们马上来尝试使用AA强大的注解功能,简化我们的代码。
@EActivity
这是我们一定会用到的第一个注解,主要用于创建Activity,它的使用方法有两种,示例如下:
第一种是带ResId的,用了这种方式,就不需要再在setContentView了,非常简洁。
@EActivity(R.layout.main) public class MyActivity extends Activity { }第二种是不带ResId的,在我们不想让AA帮我们setContentView的时候,我们仍然可以自己来进行这一设置。
@EActivity public class MyListActivity extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }这里需要注意一个问题,我们知道,在一般公司的项目中,为了能够尽可能多的重用代码,我们很可能会封装一个自己的BaseActivity,就像上面的ListActivity一样,而且更进一步,在BaseActivity中就已经加载了一个基础布局(例如有一个TitleBar)并setContentView了,然后我们继承了BaseActivity,在onCreate()中直接调用super.onCreate(),并把这个Activity具体的布局通过inflate的方式加载到BaseActivity的基础布局中去,就像下面的两个Activity的关系:
MainActivity
@EActivity public class MainActivity extends BaseActivity { @Click(R.id.btn_waterwave) void btn_waterwave(){ Intent intent = new Intent(MainActivity.this,GewalaActivity.class); startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater inflater = (LayoutInflater) this .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.activity_main, mLayout_body); } }
@EActivity public class BaseActivity extends Activity{ public LinearLayout mLayout_body; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub requestWindowFeature(Window.FEATURE_NO_TITLE); // 窗口模式 super.onCreate(savedInstanceState); setContentView(R.layout.base_module_view); mLayout_body = (LinearLayout) findViewById(R.id.Layout_body); } }MainActivity中的@Click是对于点击事件的注解,稍后详细讲解。当我们这样写我们的基础活动,并且在编程中不断通过继承来加载基础布局的时候,是非常方便的,可以减少开发量,但是使用了AA之后,像上面那样修改我们的代码会变得不可行,运行一下就会发现,位于MainActivity中的点击事件并没有被注册,如果我们采用注解的方式来获得布局当中控件的实例,就会发现会报空指针错误。
为了这个问题,我犯愁了大半晚,为此上AA在github中的issue里提问(然后被里面的老外大神p了一顿,下次应该去so...),得到了里面的管理者的耐心回答,我才明白(耗尽了我的英语表达潜力...),AA在Activity中绑定View的注入是在setContentView()方法被调用的时候进行的,因此像上面写的一样,在BaseActivity中已经调用了setContentView(),而此时基础布局里并没有包含子部局,因此在MainActivity的onCreate()中才通过inflate加载的btn_waterwave(Button)就没有办法进行事件绑定了,因此会报空指针。
所以,问题的关键在于要在加载了完整的布局后才调用setContentView(),因此我们可以对上面的两个Activity进行下面的修改:
NewMainActivity:
@EActivity public class NewMainActivity extends NewBaseActivity { @Click(R.id.btn_waterwave) void btn_waterwave(){ Intent intent = new Intent(MainActivity.this,GewalaActivity.class); startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { setToContentView(R.layout.activity_main);//必须先调用这个方法 super.onCreate(savedInstanceState); /**LayoutInflater inflater = (LayoutInflater) this .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.activity_main, mLayout_body);**/ } }
@EActivity public class NewBaseActivity extends Activity{ public LinearLayout mLayout_body; public View layout; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub requestWindowFeature(Window.FEATURE_NO_TITLE); // 窗口模式 super.onCreate(savedInstanceState); /**setContentView(R.layout.base_module_view); mLayout_body = (LinearLayout) findViewById(R.id.Layout_body);**/ } /** * 采用Android Annotations框架,需要在MainActivity的oncreate()里先调用这个方法把布局set进来,再调用 super.onCreate(savedInstanceState); * **/ public void setToContentView(int layoutResID){ LayoutInflater inflater = (LayoutInflater)BaseActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); layout = inflater.inflate(R.layout.base_module_view,null); mLayout_body = (LinearLayout)layout.findViewById(R.id.Layout_body); inflater.inflate(layoutResID,mLayout_body); } }
使用@EActivity还有两点需要注意,因为使用AA框架的时候,它会在编译过程中自动帮我们生成被注解的类的一个新的类自动帮我们绑定view和事件,因此当我们需要像以前一样调用例如“MainActivity.class”以及我们在manifast中注册这个类的时候,需要在类名后面加上“_”下划线(例如:MainActivity_.class),切记,要不依然会报错,下面我们来讲解一下另外两个在上面用到的注解。
@ViewById
这就是把我们从万恶的findviewbyid中解放的注解!有下面几种用法:
@EActivity public class MyActivity extends Activity { // Injects R.id.myEditText @ViewById //相当于EditText myEditText = (EditText)findViewById(R.id.myEditText); EditText myEditText; @ViewById(R.id.myTextView) TextView textView; }
@EActivity public class MyActivity extends Activity { @ViewById void setOneView(EditText myEditText){ // do something with myEditText } void setMultipleBeans(@ViewById EditText myEditText, @ViewById(R.id.myTextView) TextView textView){ // do something with myEditText and textView } }
@Click
这是对于点击事件的注解,你可以理解为之前对控件的setOnClickListener,不同在于,我们再也不用写一大堆的匿名类了!它有以下几种用法:
@Click(R.id.myButton)//对id为myButton的控件进行监听注册 void myButtonWasClicked() {//这个方法随喜好命名 [...]//这里相当于onclick() } @Click void anotherButton() {//不写Resid的话,这个方法名就是我们在xml中这个控件设置的id,这里就是@+id/anotherButton,将对这个控件进行绑定 [...] } @Click void yetAnotherButton(View clickedView) {//与上面的类似,不过会把这个控件的view传入 [...] } @Click({R.id.myButton, R.id.myOtherButton})//进行多个id的控件对同一个点击事件进行绑定 void handlesTwoButtons() { [...] }