編輯:關於Android編程
(如果你想快速了解ButterKnife的實現思路,可以先閱讀 ExampleActivity$InjectAdapter類以及後續的結論,然後再回過頭來閱讀 )
下面我們來看看 ButterKnife 的簡單使用。
首先我們看在沒有使用ButterKnife時,我們初始化一個Activity中的各個控件的代碼:
public class ExampleActivity extends Activity {
TextView title;
ImageView icon;
TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 通過findViewById進行視圖查找,然後進行類型轉換
title = (TextView) findViewById(R.id.title);
icon = (ImageView) findViewById(R.id.icon);
footer = (TextView) findViewById(R.id.footer);
}
}
在ExampleActivity函數的onCreate函數中,我們通常會對各個子視圖進行初始化,這些代碼看起來重復性很高,而且丑陋不堪,幾乎都要對View進行強轉,當一個布局中含有十個以上的View時,再加上為某些View添加上事件處理等,這部分的代碼將占用很大的篇幅。ButterKnife就是為了簡化這些工作而出現的,讓開發人員專注在真正有用的代碼上。使用ButterKnife之後我們的代碼變成了這樣:
public class ExampleActivity extends Activity {
@InjectView(R.id.title) TextView title;
@InjectView(R.id.icon) ImageView icon;
@InjectView(R.id.footer) TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 將Activity注入ButterKnife
ButterKnife.inject(this);
}
}
當運行完onCreate函數之後Activity中的幾個View就已經被初始化了。findViewById、強制轉換等樣板代碼被去除了,代碼變得更加簡單,使得我們可以更專注在代碼邏輯的編寫上,整個類型也更易於維護。
那麼ButterKnife的原理是什麼呢?@InjectView又是什麼?ButterKnife的inject函數又有什麼作用?
這是因為ButterKnife使用了一種叫做編譯時注解的技術(即APT),代碼在編譯時會掃描AbstractProcessor的所有子類,並且調用這些子類的process函數,在這個函數就會將所有的代碼元素傳遞進來。此時我們只需要在這個process函數中獲取所有添加了某個注解的元素,然後對這些元素進行操作,使之能夠滿足我們的需求,這樣我們就可以在編譯期對源代碼進行處理,例如生成新的類等。在運行時,我們通過一些接口對這些新生成的類進行調用以此完成我們的功能。
說了這麼多還是太抽象了,還是以小民的例子來為大家一一解除疑問吧。
小民自從知道ButterKnife之後也被它的魅力所吸引了,於是決定研究個究竟,經過一番搜索得知ButterKnife是基於編譯時注解,然後通過APT生成輔助類,然後在運行時通過inject函數調用那些生成的輔助類來完成功能。小民決定自己寫一個只支持View 的id注入的簡版ButterKnife來深入學習,這個庫被命名為SimpleDagger。
首先小民建了一個注解類,代碼如下 :
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewInjector {
int value();
}
因為我們的這個注解只支持View的id注入,因此它的目標元素是字段,它只存在在class文件中,因為一旦過了編譯期我們就不再需要它了。關於注解方面的基礎知識我們不做過多講解,對這方面不了解的同學可以先閱讀相關書籍,例如《Java編程思想》、《Java核心技術》。
在添加AbstractProcessor 之前,為了使Eclipse支持 APT 需要一些配置,可以參考 injectdagger。Android Studio要支持 APT則需要添加APT插件,有興趣的同學可以自行搜索相關解決方案。
通過 APT 來生成輔助類型
添加這個注解之後,我們還需要在編譯期對這個注解進行處理。上文說到,編譯器會在編譯時檢測所有的AbstractProcessor並且調用它的process函數來讓開發人員對代碼元素進行處理。因此我們新建一個AbstractProcessor的子類,代碼如下 :
@SupportedAnnotationTypes("org.simple.injector.anno.*")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ViewInjectorProcessor extends AbstractProcessor {
//所有注解處理器的列表
List mHandlers = new LinkedList();
//類型與字段的關聯表,用於在寫入Java文件時按類型來寫不同的文件和字段
final Map> map = new HashMap>();
// 生成輔助累的Writer類
AdapterWriter mWriter;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 注冊注解處理器
registerHandlers();
// 初始化代碼生成器
mWriter = new DefaultJavaFileWriter(processingEnv);
}
// 注冊處理器
private void registerHandlers() {
mHandlers.add(new ViewInjectHandler());
}
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
// 迭代所有的注解處理器,使得每個注解都有一個處理器,
for (AnnotationHandler handler : mHandlers) {
// 關聯ProcessingEnvironment
handler.attachProcessingEnv(processingEnv);
// 解析注解相關的信息
map.putAll(handler.handleAnnotation(roundEnv));
}
// 將解析到的數據寫入到具體的類型中
mWriter.generate(map);
return true;
}
// 代碼省略
}
在ViewInjectorProcessor類的上面我們看到如下注解@SupportedAnnotationTypes(“org.simple.injector.anno.*”), 這個注解表明這個類只支持org.simple.injector.anno路徑下的注解,我們的ViewInjector注解就是在這個包下。在該類的init函數中我們注冊了一個注解處理器,也就是ViewInjectHandler類,該類實現了AnnotationHandler接口,該接口的聲明如下 :
// 注解處理接口
public interface AnnotationHandler {
// 關聯ProcessingEnvironment
void attachProcessingEnv(ProcessingEnvironment processingEnv);
// 處理注解,將結果存儲到Map中
Map> handleAnnotation(RoundEnvironment env);
}
該接口聲明了兩個函數,一個是關聯ProcessingEnvironment,另一個是handleAnnotation函數,負責處理標識了ViewInjector注解的元素。小民的設計思路是定義一個AnnotationHandler接口,每個實現類處理一種類型的注解,例如ViewInjectHandler只處理ViewInject注解。下面我們看看ViewInjectHandler的核心代碼 :
public class ViewInjectHandler implements AnnotationHandler {
ProcessingEnvironment mProcessingEnv;
@Override
public void attachProcessingEnv(ProcessingEnvironment processingEnv) {
mProcessingEnv = processingEnv;
}
@Override
public Map> handleAnnotation(RoundEnvironment roundEnv) {
Map> annotationMap = new HashMap>();
// 1、獲取使用ViewInjector注解的所有元素
Set elementSet = roundEnv.getElementsAnnotatedWith(ViewInjector.class);
for (Element element : elementSet) {
// 2、獲取被注解的字段
VariableElement varElement = (VariableElement) element;
// 3、獲取字段所在類型的完整路徑名,比如一個TextView所在的Activity的完整路徑,也就是變量的宿主類
String className = getParentClassName(varElement);
// 4、獲取這個宿主類型的所有元素,例如某個Activity中的所有注解對象
List cacheElements = annotationMap.get(className);
if (cacheElements == null) {
cacheElements = new LinkedList();
}
// 將元素添加到該類型對應的字段列表中
cacheElements.add(varElement);
// 以宿主類的路徑為key,所有字段列表為value,存入map.
// 這裡是將所在字段按所屬的類型進行分類
annotationMap.put(className, cacheElements);
}
return annotationMap;
}
// 代碼省略
}
在handleAnnotation函數中小民獲取了所有被ViewInject注解標識了的VariableElement元素,然後將這些元素按照宿主類進行分類存到一個map中,key就是宿主類的完整類路徑,value就是這個宿主類中的所有被標識了ViewInject的VariableElement元素列表。例如將上述ExampleActivity的示例替換成小民的SimpleDagger,使用ViewInject注解標識中三個View,代碼如下 :
package com.simple.apt;
public class ExampleActivity extends Activity {
@ViewInject (R.id.title) TextView title;
@ViewInject (R.id.icon) ImageView icon;
@ViewInject (R.id.footer) TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 其他代碼暫時省略
SimpleDagger.inject(this);
}
}
那麼此時ExampleActivity的完整路徑為com.simple.apt.ExampleActivity,這個完整路徑我們可以通過VariableElement元素獲取到,這些VariableElement就是代表了ExampleActiivty中的title、icon、footer三個對象。因此通過ViewInjectHandler的handleAnnotation處理之後我們的map中就含有了以com.simple.apt.ExampleActivity為key,以title、icon、footer三個成員變量對應的VariableElement列表為value的數據。
此時執行到process函數的最後一步,這裡調用了AdapterWriter來生成輔助類,這個輔助類要生成的代碼素材就是我們上述的VariableElement元素列表,調用的是AdapterWriter的generate函數,在AdapterWriter之下我們還建立了一個AbsWriter來封裝一些通用邏輯,AbsWriter核心代碼如下 :
public abstract class AbsWriter implements AdapterWriter {
ProcessingEnvironment mProcessingEnv;
Filer mFiler;
// 代碼省略
@Override
public void generate(Map> typeMap) {
Iterator>> iterator = typeMap.entrySet().iterator();
while (iterator.hasNext()) {
Entry> entry = iterator.next();
List cacheElements = entry.getValue();
if (cacheElements == null || cacheElements.size() == 0) {
continue;
}
// 取第一個元素來構造注入信息
InjectorInfo info = createInjectorInfo(cacheElements.get(0));
Writer writer = null;
JavaFileObject javaFileObject;
try {
// 1、創建源文件,也就是生成輔助類
javaFileObject = mFiler.createSourceFile(info.getClassFullPath());
writer = javaFileObject.openWriter();
// 2、寫入package, import, class以及findViews函數等代碼段
generateImport(writer, info);
// 3、寫入該類中的所有字段到findViews方法中
for (VariableElement variableElement : entry.getValue()) {
writeField(writer, variableElement, info);
}
// 4、寫入findViews函數的大括號以及類的大括號
writeEnd(writer);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtil.closeQuitly(writer);
}
}
}
// 代碼省略
}
在AbsWriter的generate函數中,我們定義了一個生成輔助類的邏輯骨架,分別為獲取宿主類型的所有元素,並且通過第一個元素獲取宿主類所在的包以及構建輔助類的類名等,然後創建一個新的java類,最後分別寫入import、所有被注解的元素等信息寫入到輔助類當中,所有生成的輔助類都是InjectAdapter的子類。實現代碼如下的功能在DefaultJavaFileWriter類中,核心代碼如下 :
public class DefaultJavaFileWriter extends AbsWriter {
// 代碼省略
// 寫入import以及類前面的類型聲明
@Override
protected void generateImport(Writer writer, InjectorInfo info)
throws IOException {
writer.write("package " + info.packageName + " ;");
writer.write("\n\n");
writer.write("import org.simple.injector.adapter.InjectAdapter ;");
writer.write("\n");
writer.write("import org.simple.injector.util.ViewFinder;");
writer.write("\n\n\n");
writer.write("/* This class is generated by Simple ViewInjector, please don't modify! */ ");
writer.write("\n");
writer.write("public class " + info.newClassName
+ " implements InjectAdapter<" + info.classlName + "> { ");
writer.write("\n");
writer.write("\n");
// 查找方法
writer.write(" public void injects(" + info.classlName
+ " target) { ");
writer.write("\n");
}
// 寫入一個結尾的大括號
@Override
protected void writeEnd(Writer writer) throws IOException {
writer.write(" }");
writer.write("\n\n");
writer.write(" } ");
}
// 寫入字段
@Override
protected void writeField(Writer writer, VariableElement element, InjectorInfo info)
throws IOException {
ViewInjector injector = element.getAnnotation(ViewInjector.class);
String fieldName = element.getSimpleName().toString();
writer.write(" target." + fieldName + " = ViewFinder.findViewById(target, "
+ injector.value() + " ) ; ");
writer.write("\n");
}
}
在DefaultJavaFileWriter中分別寫入了輔助類的各個部分,最終的是寫入字段的部分,也就是writeField函數。在該函數中,小民獲取了這個字段的名字,並且寫下了一行如下一行代碼 :
target.fieldName = ViewFinder.findViewBydId(target, ViewInject注解的值);
其實這就是一個初始化某個View的語句,這個target在這個例子中就是ExampleActivity,這個ViewInject注解的值就是View的id,我們知道每個含有id的View最終都會在R類中生成一個整型的數值,這裡的view id就是這個整型數值。需要注意的是這些被添加注解的字段都必須是非私有的,否則不能通過target.fieldName的形式直接訪問。這些初始化代碼都被寫到了InjectAdapter子類的inject函數中,inject函數傳遞一個target參數,這個target就是元素所在的類,比如ExampleActivity,而生成的輔助類的名稱格式為宿主類+”
InjectAdapter 接口
InjectAdapter的聲明如下 :
public interface InjectAdapter {
void injects(T target);
}
ExampleActivity$InjectAdapter 類
這相當於我們為每個元素都生成一行初始化代碼來替換手動在ExampleActiivty中進行findViewById,當我們在ExampleAcivity的onCreate函數中調用SimpleDagger的inject函數時,會將ExampleActivity傳遞到InjectAdapter中,因此最後為ExampleActivity生成的輔助類就成為了如下這樣 :
public class ExampleActivity$InjectAdapter
implements InjectAdapter {
public void injects(ExampleActivity target) {
target.title = ViewFinder.findViewById(target, 2131099648 ) ;
target.icon = ViewFinder.findViewById(target, 2131099649 ) ;
target.footer=ViewFinder.findViewById(target, 2131099332 ) ;
}
}
當調用SimpleDagger的inject時就會先通過傳遞進來的類名構建一個InjectAdapter子類的類名,例如傳遞進來的是ExampleActivity,那麼此時的輔助類的類名為
ExampleActivity$InjectAdapter,它InjectAdapter的子類。拿到完整類名之後再反射構建一個對象,然後轉換為InjectAdapter,最後調用inject函數。而這個生成的ExampleActivity$InjectAdapter的inject函數中又對每個View進行了findViewBydId,也就是對它們進行了初始化。至此,這些View字段就被自動初始化了!
我們最後再來捋一捋這個過程,大致分為如下幾步 :
通過ViewInject注解標識一些View成員變量; 通過ViewInjecyProcessor捕獲添加了ViewInject注解的元素,並且按照宿主類進行分類; 為每個含有ViewInject注解的宿主類生成一個InjectAdapter輔助類,並且在它的inject函數中生成初始化View的代碼; 在SimpleDagger的inject函數中構建生成的輔助類,此時內部會它這個InjectAdapter輔助類的inject函數,這個函數中又會初始化宿主類中的View成員變量,至此,View就已經被初始化了。
SimpleDagger的完整代碼在這裡,有興趣的同學可以下載下來進行學習以及擴展。
需要注意的是在eclipse中使用APT需要添加JRE庫的引用,在Android Studio則需要引用APT的插件。
一、GPU硬件加速1、概述GPU英文全稱Graphic Processing Unit,中文翻譯為“圖形處理器”。與CPU不同,GPU是專門為處理
Android特效專輯(十)——點擊水波紋效果實現,邏輯清晰實現簡單 這次做的東西呢,和上篇有點類似,就是用比較簡單的邏輯思路去實現一些比較好玩的
前言屬性動畫(Property Animation)系統是一個更加強大的框架,它幾乎允許你為任何東西設置動畫。不管一個對象是否需要繪制到屏幕上面,你都可以定義一個動畫讓這
1 簡介Android大型項目中為了減小apk的體積,可以采用插件化的方法,即一些不常用的功能獨立成插件,當用戶需要的使用的時候再從服務器上下載回來,動態加載。這樣就避免