本篇文章主要講解 Java 註解在Android中的常見用法html
Java 註解(Annotation)又稱 Java 標註,是 JDK5.0 引入的一種註釋機制。 Java 語言中的類、方法、變量、參數和包等均可以被標註。和 Javadoc 不一樣,Java 標註能夠經過反射獲取標註內容。在編譯器生成類文件時,標註能夠被嵌入到字節碼中。Java 虛擬機能夠保留標註內容,在運行時能夠獲取到標註內容 。 固然它也支持自定義 Java 標註。java
Java 定義了一套註解,共有 7 個,3 個在 java.lang 中,剩下 4 個在 java.lang.annotation 中。git
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
複製代碼
package java.lang.annotation;
public enum ElementType {
TYPE, /* 類、接口(包括註釋類型)或枚舉聲明 */
FIELD, /* 字段聲明(包括枚舉常量) */
METHOD, /* 方法聲明 */
PARAMETER, /* 參數聲明 */
CONSTRUCTOR, /* 構造方法聲明 */
LOCAL_VARIABLE, /* 局部變量聲明 */
ANNOTATION_TYPE, /* 註釋類型聲明 */
PACKAGE /* 包聲明 */
}
複製代碼
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation信息僅存在於編譯器處理期間,編譯器處理完以後就沒有該Annotation信息了 */
CLASS, /* 編譯器將Annotation存儲於類對應的.class文件中。默認行爲 */
RUNTIME /* 編譯器將Annotation存儲於class文件中,而且可由JVM讀入 */
}
複製代碼
更多基本知識點參考,有更全面的,這裏就不過多描述了 Java 註解詳細講解以及基礎語法 github
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.text_welcome)
TextView text_welcome;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUntil.inject(this);
initData();
}
private void initData() {
text_welcome.setText("我是經過註解後成功修改的值");
}
}
複製代碼
總結:這個 MainActivity 沒啥好講解的,相似於黃油刀用法,使用了@ContentView 和 @ViewInject 這倆個自定義註解分別獲得對應的佈局和對應的控件,並最後,修改了 text_welcome 的顯示文本面試
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
int value();
}
複製代碼
總結:這裏使用了 @Target, @Retention, @interface 這三個關鍵代碼,如今分別對這三個進行講解markdown
它的做用是指定 Deprecated 的策略是 RetentionPolicy.RUNTIME。這就意味着,編譯器會將Deprecated 的信息保留在 .class 文件中,而且能被虛擬機讀取【參考上面註解的組成部分】 2. @Target(ElementType.TYPE) 它的做用是指定該 Annotation 的類型是 ElementType.TYPE。這就意味着,ContentView 是來修飾"類、接口(包括註釋類型)或枚舉聲明"的註解。【參考上面註解的組成部分】 3. @interface 它的做用是實現了 java.lang.annotation.Annotation 接口,即該註解就是一個Annotation。 定義 Annotation 時,@interface 是必須的。ide
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
int value();
}
複製代碼
總結:上面的 @Retention 與 @interface 已經說了,而 @Target(ElementType.FIELD),參考上面註解的組成部分oop
public class ViewUntil {
public static void inject(Object context) {
injectLayout(context);
injectView(context);
}
private static void injectView(Object context) {
//獲取對應的綁定對象
Class<?> aClass = context.getClass();
//獲取對應綁定對象的全部成員屬性
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
//獲得每個 被註解綁定的控件
ViewInject viewInject = field.getAnnotation(ViewInject.class);
//固然存在有些成員屬性,沒有被註解綁定,因此這裏跳過該變量便可
if (viewInject == null) {
continue;
}
//獲得每個被綁定的控件id
int valueId = viewInject.value();
try {
//經過反射,拿到綁定對象的 findViewById 方法名
Method method = aClass.getMethod("findViewById", int.class);
//拿到對應方法名後,調用對應方法
View view = (View) method.invoke(context, valueId);
// 將 view btn 產生聯繫
field.setAccessible(true);
field.set(context, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void injectLayout(Object context) {
int layouyId = 0;
Class<?> clazz = context.getClass();
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView == null) {
return;
}
//拿到註解裏面的設置的佈局id
layouyId = contentView.value();
try {
// 經過反射拿到 setContentView 方法,而後調用該方法時,將註解獲得的佈局id,傳入進去,從而實現 加載佈局的功能
Method contentMethod = context.getClass().getMethod("setContentView", int.class);
contentMethod.invoke(context, layouyId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
總結:該類目前有三個方法:inject,injectView,injectLayout佈局
其餘的註釋寫的很全面,想必應該能看懂吧。post
到這,咱們實現了佈局、控件的綁定。但並無實現控件的事件綁定(點擊、長按)相關的。 接下來的內容涉及到動態代理知識相關,建議先看完 Android 靜態代理與動態代理詳解
在開始以前,咱們先整理一下需求。控件的事件綁定,分爲點擊事件、長按事件。 一個控件可能會有多個事件; 一個事件也可能會綁定多個控件; 徹底是多對多的關係! 因而乎咱們須要自定義屬於本身的註解(@EventBase), 用來修飾對應的點擊事件(@OnClick)和長按事件(@OnLongClick)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
//setOnLongClickListener
String listenerSetter();
/** * 事件監聽的類型 * @return * OnLongClickListener.class */
Class<?> listenerType();
/** * 事件被觸發以後,執行的回調方法的名稱 * @return * * onLongClick */
String callbackMethod();
}
複製代碼
總結:這裏 @Target(ElementType.ANNOTATION_TYPE) 它的做用是指定 Inherited 的類型是 ANNOTATION_TYPE。這就意味着,@Inherited 只能被用來標註 "Annotation 類型"。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter="setOnClickListener",listenerType = View.OnClickListener.class,callbackMethod ="onClick" )
public @interface OnClick{
int[] value();
}
複製代碼
總結:這裏注意,多了一個 @EventBase 來修飾 OnClick 註解,表示 OnClick 對應類型,以及對應出發的方法都和系統的點擊事件一一對應了起來。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter="setOnLongClickListener",listenerType = View.OnLongClickListener.class,callbackMethod ="onLongClick" )
public @interface OnLongClick {
int[] value();
}
複製代碼
總結:同上, @EventBase 修飾的 OnLongClick 註解,和系統的長按事件一一對應了起來。
注:這裏的長按和點擊對應的註解都用到了 @EventBase 註解,這裏可能會被誤認爲,這個似於類的繼承,擁有了父類的公開的內容。但千萬不要往這方面想。註解是不能被繼承的,只能拿來修飾其餘註解,這也是面試官很是喜歡問的一點。
public class ViewUntil {
public static void inject(Object context) {
injectLayout(context);
injectView(context);
injectClick(context);
}
private static void injectClick(Object context) {
//拿到綁定的對象
Class<?> clazz = context.getClass();
//獲取綁定對象全部的方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<?> annotionType = annotation.annotationType();
EventBase eventBase = annotionType.getAnnotation(EventBase.class);
if (eventBase == null) {
continue;
}
// 拿到事件三要素
/** * setOnLongClickListener */
String listenerSetter = eventBase.listenerSetter();
/** * OnLongClickListener.class */
Class<?> listenerType = eventBase.listenerType();
/** * 事件被觸發以後,執行的回調方法的名稱 * @return * * onLongClick */
String callBackMethod = eventBase.callbackMethod();
try {
Method valueMethod = annotionType.getDeclaredMethod("value");
int[] viewId = (int[]) valueMethod.invoke(annotation);
for (int id : viewId) {
// 有多少個按鈕 id ---》findviewById View.setOnClickerListener
Method findViewById = clazz.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context, id);
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
Method setListener = view.getClass().getMethod(listenerSetter, listenerType);
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType}, listenerInvocationHandler);
setListener.invoke(view, proxy);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/** * btn button ====> value R.id.btn ===>findviewbyid * btn.setOnClickListener(new View.OnClickListener() { * @Override * public void onClick(View v) { * * } * }); */
// setOnClickListener Method 設置
//
}
}
private static void injectView(Object context) {
Class<?> aClass = context.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject == null) {
continue;
}
int valueId = viewInject.value();
try {
Method method = aClass.getMethod("findViewById", int.class);
View view = (View) method.invoke(context, valueId);
// view btn 產生聯繫
field.setAccessible(true);
field.set(context, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void injectLayout(Object context) {
int layouyId = 0;
Class<?> clazz = context.getClass();
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView == null) {
return;
}
//拿到註解裏面的設置的佈局id
layouyId = contentView.value();
try {
// 經過反射拿到 setContentView 方法,而後調用該方法時,將註解獲得的佈局id,傳入進去,從而實現 加載佈局的功能
Method contentMethod = context.getClass().getMethod("setContentView", int.class);
contentMethod.invoke(context, layouyId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
總結:這裏也沒啥可說的,只要理解了上面註解的使用,以及動態代理使用,就很是容易明白。 剛開始先拿到 對應方法 的 EventBase 註解,而後獲得註解裏面的事件三要素,接着經過反射找到對應的控件,最後將事件三要素以及對應控件經過動態代理的方式 實現了點擊、長按事件。
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.text_welcome)
TextView text_welcome;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUntil.inject(this);
initData();
}
private void initData() {
text_welcome.setText("我是經過註解後成功修改的值");
}
@OnLongClick({R.id.btn_one, R.id.btn_two})
public boolean onLongClick(View view) {
switch (view.getId()) {
case R.id.btn_one:
Toast.makeText(this, "你在長按按鈕----------》1", Toast.LENGTH_LONG).show();
break;
case R.id.btn_two:
Toast.makeText(this, "你在長按按鈕----------》2", Toast.LENGTH_LONG).show();
break;
}
return false;
}
@OnClick({R.id.btn_one, R.id.btn_two})
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_one:
Toast.makeText(this, "你在點擊按鈕----------》1", Toast.LENGTH_LONG).show();
break;
case R.id.btn_two:
Toast.makeText(this, "你在點擊按鈕----------》2", Toast.LENGTH_LONG).show();
break;
}
}
}
複製代碼
//處理器
public class ListenerInvocationHandler implements InvocationHandler {
private Object context;
private Method activityMethod;
private final String TAG = "Handler";
public ListenerInvocationHandler(Object context, Method activityMethod) {
this.context = context;
this.activityMethod = activityMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i(TAG, "invoke: ----調用前");
return activityMethod.invoke(context, args);
}
}
複製代碼