Android應用中使用執行時註解
其實非常簡單,直接上程式碼:本文主要是替代傳統的findViewById()的功能,就是在我們Activity中不需要再使用findViewById()去給View賦值了,通過註解在執行階段自動賦值。以及setOnClickListener()也是一樣的原理。使用註解和反射技術。
1. 定義自己的annotation註解。
定義findViewbyId這個功能的註解
package com.xxx.app.inject; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface InjectView { int value() default (int) -1; }
定義setOnclickListener的註解
package com.xxx.app.inject; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface InjectClick { int[] value(); }2. 定義自己的註解處理類:
package com.xxx.app.inject; import android.app.Activity; import android.view.View; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Injector { public static void injectView(Object obj, Object root) { Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Annotation[] annotations = field.getAnnotations(); if (annotations != null) { for (Annotation annotation : annotations) { if (annotation instanceof InjectView) { InjectView injectView = (InjectView) annotation; int value = injectView.value(); if (value != -1) { try { View view = getViewByRoot(root, value); field.set(obj, view); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } break; } } } } } public static void injectClick(Object obj, Object root) { Method[] methods = obj.getClass().getDeclaredMethods(); for (Method method : methods) { Annotation[] annotations = method.getAnnotations(); if (annotations != null) { for (Annotation annotation : annotations) { if (annotation instanceof InjectClick) { InjectClick inject = (InjectClick) annotation; int[] value = inject.value(); if (value != null && value.length > 0) { View.OnClickListener listener = (View.OnClickListener) obj; try { for (int res : value) { View view = getViewByRoot(root, res); if (view == null) { throw new NullPointerException(); } view.setOnClickListener(listener); } } catch (IllegalArgumentException e) { e.printStackTrace(); } } } else if (annotation instanceof InjectLongClick) { InjectLongClick inject = (InjectLongClick) annotation; int[] value = inject.value(); if (value != null && value.length > 0) { View.OnLongClickListener listener = (View.OnLongClickListener) obj; try { for (int res : value) { View view = getViewByRoot(root, res); if (view == null) { throw new NullPointerException(); } view.setOnLongClickListener(listener); } } catch (IllegalArgumentException e) { e.printStackTrace(); } } } } } } } public static View getViewByRoot(Object root, int res) { View view = null; if (root instanceof Activity) { view = ((Activity)root).findViewById(res); } return view; } }
3. Activity中使用註解:
@InjectView(R.id.action_back) private ImageView actionBack; @InjectView(R.id.site_top_bg) private ImageView mSiteTopBg; @InjectView(R.id.site_name) private TextView mSiteName; @InjectView(R.id.site_producter) private TextView mProducter; @InjectView(R.id.site_logo) private ImageRoundView mSiteLogo; @InjectView(R.id.site_description) private TextView mSiteDescription; @InjectView(R.id.viewPager) private ViewPager mViewPager;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_site); Injector.injectView(this, this); Injector.injectClick(this, this); }
@Override @InjectClick({R.id.action_back}) public void onClick(View v) { int id = v.getId(); switch (id){ case R.id.action_back: SiteDetailActivity.this.finish(); break; } }
=============================================
一、佈局檔案的註解
我們在Android開發的時候,總是會寫到setContentView方法,為了避免每次都寫重複的程式碼,我們需要使用註解來代替我們做這個事情,只需要在類Activity上宣告一個ContentView註解和對應的佈局檔案就可以了。
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
}
}
從上面可以看到,上面程式碼在MainActivity上面使用到了ContentView註解,下面我們來看看ContentView註解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
這個註解很簡單,它有一個int的value,用來存放佈局檔案的id,另外它註解的物件為一個型別,需要說明的是,註解的生命週期會一直到執行時,這個很重要,因為程式是在執行時進行反射的,我們來看看ViewUtils.injectContentView(this)方法,它進行的就是註解的處理,就是進行反射呼叫setContentView()方法。
public static void injectContentView(Activity activity) {
Class a = activity.getClass();
if (a.isAnnotationPresent(ContentView.class)) {
// 得到activity這個類的ContentView註解
ContentView contentView = (ContentView) a.getAnnotation(ContentView.class);
// 得到註解的值
int layoutId = contentView.value();
// 使用反射呼叫setContentView
try {
Method method = a.getMethod("setContentView", int.class);
method.setAccessible(true);
method.invoke(activity, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
如果對Java註解比較熟悉的話,上面程式碼應該很容易看懂。
二、欄位的註解
除了setContentView之外,還有一個也是我們在開發中必須寫的程式碼,就是findViewById,同樣,它也屬於簡單但沒有價值的編碼,我們也應該使用註解來代替我們做這個事情,就是對欄位進行註解。
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.btn1)
private Button mButton1;
@ViewInject(R.id.btn2)
private Button mButton2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
ViewUtils.injectViews(this);
}
}
上面我們看到,使用ViewInject對兩個Button進行了註解,這樣我們就是不用寫findViewById方法,看上去很神奇,但其實原理很簡單。我們先來看看ViewInject註解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
這個註解也很簡單,就不說了,重點就是註解的處理了。
public static void injectViews(Activity activity) {
Class a = activity.getClass();
// 得到activity所有欄位
Field[] fields = a.getDeclaredFields();
// 得到被ViewInject註解的欄位
for (Field field : fields) {
if (field.isAnnotationPresent(ViewInject.class)) {
// 得到欄位的ViewInject註解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
// 得到註解的值
int viewId = viewInject.value();
// 使用反射呼叫findViewById,併為欄位設定值
try {
Method method = a.getMethod("findViewById", int.class);
method.setAccessible(true);
Object resView = method.invoke(activity, viewId);
field.setAccessible(true);
field.set(activity, resView);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
上面的註釋很清楚,使用的也是反射呼叫findViewById函式。
三、事件的註解
在Android開發中,我們也經常遇到setOnClickListener這樣的事件方法。同樣我們可以使用註解來減少我們的程式碼量。
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
ViewUtils.injectEvents(this);
}
@OnClick({R.id.btn1, R.id.btn2})
public void clickBtnInvoked(View view) {
switch (view.getId()) {
case R.id.btn1:
Toast.makeText(this, "Button1 OnClick", Toast.LENGTH_SHORT).show();
break;
case R.id.btn2:
Toast.makeText(this, "Button2 OnClick", Toast.LENGTH_SHORT).show();
break;
}
}
}
佈局檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="#70DBDB"
android:orientation="vertical"
tools:context="statusbartest.hpp.cn.statusbartest.MainActivity">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test1"/>
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test2"/>
</LinearLayout>
可以看到,上面我們沒有對Button呼叫setOnClickListener,但是當我們點選按鈕的時候,就會回撥clickBtnInvoked方法,這裡我們使用的就是註解來處理的。下面先來看看OnClick註解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")
public @interface OnClick {
int[] value();
}
可以看到這個註解使用了一個自定義的註解。
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
Class listenerType();
String listenerSetter();
String methodName();
}
下面來看看註解的處理。
public static void injectEvents(Activity activity) {
Class a = activity.getClass();
// 得到Activity的所有方法
Method[] methods = a.getDeclaredMethods();
for (Method method : methods) {
// 得到被OnClick註解的方法
if (method.isAnnotationPresent(OnClick.class)) {
// 得到該方法的OnClick註解
OnClick onClick = method.getAnnotation(OnClick.class);
// 得到OnClick註解的值
int[] viewIds = onClick.value();
// 得到OnClick註解上的EventBase註解
EventBase eventBase = onClick.annotationType().getAnnotation(EventBase.class);
// 得到EventBase註解的值
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
String methodName = eventBase.methodName();
// 使用動態代理
DynamicHandler handler = new DynamicHandler(activity);
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType }, handler);
handler.addMethod(methodName, method);
// 為每個view設定點選事件
for (int viewId : viewIds) {
try {
Method findViewByIdMethod = a.getMethod("findViewById", int.class);
findViewByIdMethod.setAccessible(true);
View view = (View) findViewByIdMethod.invoke(activity, viewId);
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.setAccessible(true);
setEventListenerMethod.invoke(view, listener);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
這個程式碼相對上面的要複雜一些,它使用到了動態代理,關於動態代理的基本用法可以看看前面我提到的預備知識。
public class DynamicHandler implements InvocationHandler {
private final HashMap<String, Method> methodMap = new HashMap<String, Method>(
1);
// 因為傳進來的為activity,使用弱引用主要是為了防止記憶體洩漏
private WeakReference<Object> handlerRef;
public DynamicHandler(Object object) {
this.handlerRef = new WeakReference<Object>(object);
}
public void addMethod(String name, Method method) {
methodMap.put(name, method);
}
// 當回到OnClickListener的OnClick方法的時候,它會呼叫這裡的invoke方法
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 得到activity例項
Object handler = handlerRef.get();
if (handler != null) {
// method對應的就是回撥方法OnClick,得到方法名
String methodName = method.getName();
// 得到activtiy裡面的clickBtnInvoked方法
method = methodMap.get(methodName);
if (method != null) {
// 回撥clickBtnInvoked方法
return method.invoke(handler, objects);
}
}
return null;
}
}
基本的看註釋就應該差不多了。
相關文章
- 使用 ARChon 執行時環境在 Ubuntu 上執行 Android 應用UbuntuAndroid
- 【Android】註解框架(二) 基礎知識(Java註解)& 執行時註解框架Android框架Java
- Java 註解及其在 Android 中的應用JavaAndroid
- 使用spring @Scheduled註解執行定時任務、Spring
- Android 中註解的使用Android
- Java註解解析-基礎+執行時註解(RUNTIME)Java
- Java 執行時(RUNTIME)註解詳解Java
- Android 7.0推送時間曝光!應用執行快6倍Android
- Android 中優雅地使用註解Android
- Android應用中Clean架構使用詳解Android架構
- Android中執行緒的使用Android執行緒
- AssetHook:Android應用資源資料執行時編輯工具HookAndroid
- Spring中註解大全和應用Spring
- HttpRuntime應用程式的執行時HTTP
- 在 OpenFunction 中執行 Serverless 應用FunctionServer
- 使用SAP BSP應用執行VueVue
- Android中SQLite應用詳解AndroidSQLite
- Runtime-iOS執行時應用篇iOS
- Android註解使用之ButterKnife 8.0註解使用介紹Android
- android 7.0釋出時間曝光 Android牛軋糖應用執行快6倍Android
- PyQt應用程式中的多執行緒:使用Qt還是Python執行緒?QT執行緒Python
- Android 6.0 執行時許可權詳解Android
- 使用forever執行nodejs應用NodeJS
- 如何在 Ubuntu 中再次登入時還原上次執行的應用Ubuntu
- 分散式應用執行時 Dapr 1.7 釋出分散式
- 【Android】註解框架(三) 編譯時註解,手寫ButterKnifeAndroid框架編譯
- Android中子執行緒更新主執行緒UI和ProgressBar的應用Android執行緒UI
- Docker容器中執行.Net Core應用程式Docker
- iOS中多執行緒之GCD應用iOS執行緒GC
- 執行時應用自我保護(RASP):應用安全的自我修養
- Android註解使用之使用Support Annotations註解優化程式碼Android優化
- 在Docker中,可以在一個容器中同時執行多個應用程序嗎?Docker
- Android中獲取正在執行的應用程式-----ActivityManager.RunningAppProcessInfo類詳解(二)AndroidAPP
- Android 中的註解深入探究Android
- Android編譯時註解框架系列1-什麼是編譯時註解Android編譯框架
- Ooui:在瀏覽器中執行.NET應用UI瀏覽器
- Android RxJava應用例項講解:你該什麼時候使用RxJava?AndroidRxJava
- Chromebook 也許很快就能執行所有的 Android 應用ChromeAndroid