網站首頁 編程語言 正文
自定義注解
1) 先定義布局文件注入
//注解的作用域在類上
@Target(ElementType.TYPE)
//讓保持性策略為運行時態,將注解編碼到class文件中,讓虛擬機讀取
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();//使用時直接@ContentView(R.layout.xxx)指定的R.layout.xxx就是布局文件,會自動注入布局文件
}
2) 布局中控件注入文件
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
3) 控件的點擊響應注入文件
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface OnClick {
int[] value();
}
使用自定義注解
@ContentView(R.layout.activity_test)
public class TestActivity extends AppCompatActivity implements View.OnClickListener{
@ViewInject(R.id.edit_text)
EditText mEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
injectContentView(this);
injectView(this);
injectEvent(this);
}
@OnClick(R.id.button)
@Override
public void onClick(View v) {
Toast.makeText(TestActivity.this,"點擊成功"+mEditText.getText().toString(),Toast.LENGTH_SHORT).show();
}
}
通過反射機制獲取注解參數
Method相關方法的介紹:
method.invoke(Object obj,Object args[])的作用就是調用method類代表的方法,其中obj是對象名,args是傳入method方法的參數
- getMethods(): 獲得類的public類型的方法
- getMethod(String name, Class[] params): 獲得類的特定方法,name參數指定方法的名字,params參數指定方法的參數類型
- getDeclaredMethods(): 獲取類中所有的方法(public、protected、default、private)
- getDeclaredMethod(String name, Class[] params): 獲得類的特定方法,name參數指定方法的名字,params參數指定方法的參數類型
1. 布局文件獲取
public static void injectContentView(Activity activity){
//獲取activity的類實例
Class<? extends Activity> clazz = activity.getClass();
//獲取到activity的ContentView注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if(contentView!=null){
//如果activity上面存在這個注解的話,就取出這個注解對應的value值,就是前面設置的布局
int layoutId=contentView.value();
try {
//利用反射調用setContentView方法,完成注入
Method setViewMethod = clazz.getMethod("setContentView", int.class);
setViewMethod.invoke(activity,layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 控件獲取實現
private static void injectView(Activity activity){
//獲取activity的類實例
Class<? extends Activity> clazz = activity.getClass();
//獲取activity的所有成員變量
Field[] fields = clazz.getDeclaredFields();
//遍歷成員變量,獲取成員變量上的ViewInject注解
for(Field field:fields){
//獲取字段上面的注解對象,同有則繼續下一個字段
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if(viewInject!=null){
//獲取ViewInject注解的View的id
int viewId = viewInject.value();
//獲取控件
View view = activity.findViewById(viewId);
try {
//設置field為可訪問,就算私有的也能訪問到,能夠提高效率
field.setAccessible(true);
//將該控件設置給field對象
field.set(activity,view);//將activity對象的view控件設置給屬性對應上面的例子是mEditText=findViewById(viewId)
}catch (IllegalAccessException e){
e.printStackTrace();
}
}
}
}
3. 控件點擊響應
private static void injectEvent(final Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
//獲取所有方法(私有方法也可以獲取到)
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//獲取方法上面的OnClick注解
OnClick click = method.getAnnotation(OnClick.class);
//有則繼續下面代碼
if (click != null) {
//獲取注解中的數據,因可以給多個button綁定點擊事件,因此定義的注解類時使用的是int[]數據類型
int[] viewId = click.value();
//在需要發射時將method的setAccessible設置為true,可以提高反射速度,原因是設置為true后則跳過了訪問檢查,即使private修飾的也可以訪問。
method.setAccessible(true);
//設置一個代理對象,當調用setOnClickListener時,把代理對象傳進去,當點擊發生時,就會invoke方法,可以調用帶有onClick注解的method方法
//此時的listener就是actvivty實現View.OnClickListener接口,并實現其接口中onClick方法的對象,其實也就是返回的invoke對象。
Object listener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
//這個invoke,其實就是調用的意思,第一個參數就是被代理的對象,第二個參數就是調用的方法,第三個參數是被調用方法的參數數組。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "method"+method);
Log.d(TAG, "args "+args);
//執行activity對象,參數args的方法,activity對象就是當前的Activity,并且已經實現View.OnClickListener
//也就是說如果點擊了控件將會執行實現了View.OnClickListener的Activity的onClick方法
Object invoke = method.invoke(activity, args);
return invoke;
}
});
//listener是一個OnClickListener類的對象,因為method傳的是activity,所以是當前類的OnClickListener對象
//method是onClick方法
try {
for (int id : viewId) {
//獲取相應的控件
View v = activity.findViewById(id);
//"setOnClickListener"是方法名,View.OnClickListener.class方法參數的類型
Method setClickListener = v.getClass().getMethod("setOnClickListener", View.OnClickListener.class);
Log.d(TAG, "injectEvent: "+listener+"v.getClass()"+v);
//View類中的setOnClickListener方法需要一個OnClickListener類型的參數也就是我們的代理對象
//我們需要將對應的控件進行設置
//這時當我們點擊v(也就是我們找到的控件)時就會調用代理對象的onClick方法。而代理對象的onClick方法就是Activity中實現接口的onClick方法。相當于執行方法setOnClickListener(listener)。而這個listener就是我們上面代理(當前activity實現OnClicker接口的對象)對象。
setClickListener.invoke(v, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
上面的實現都是在運行時通過反射完成注入,但它會對性能有一定損耗,而Butterknife用的是APT(Annotation Processing Tool)編譯時解析技術。它在Java代碼編譯成Java字節碼時就已經處理了@Bind, @OnClick這些注解。它的原理是讀入Java源代碼,解析注解,生成新的Java代碼,新生成的Java代碼最后被編譯成Java字節碼。
原文鏈接:https://blog.csdn.net/ChenYiRan123456/article/details/125950932
相關推薦
- 2022-11-13 淘寶NPM鏡像 & cnpm
- 2022-02-18 ImportError:can't import name 'Flask'
- 2022-07-23 .Net創建型設計模式之工廠方法模式(Factory?Method)_基礎應用
- 2022-08-16 Kotlin實用語法糖空安全類型轉換及相等性判斷_Android
- 2023-02-14 解決React報錯React.Children.only?expected?to?receive?s
- 2023-07-06 springBoot JWT實現websocket的token登錄攔截認證
- 2022-07-29 Docker容器的加載分層原理及commit鏡像_docker
- 2022-07-23 C++深入細致探究二叉搜索樹_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支