Android 自己打造IOC註解框架
阿新 • • 發佈:2018-11-11
Android中IOC框架就是注入控制元件和佈局或者說是設定點選監聽,網上有很多成熟的註解框架例如xUtils,afinal,butterknife等等。你可能會問,既然已經有好的框架為何還要造輪子?因為,首先我是學習,學習框架的設計以及實現,其次是拓展,適合自己的輪子才是好輪子,所以我添加了判斷網路狀態的註解。此處特別感謝輝哥,他的技術分享是我的楷模。
首先看看最終完成的效果
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.tv_hello)
private TextView tv_hello;
@ViewById (R.id.iv_img)
private ImageView iv_img;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
tv_hello.setText("你好,IOC");
}
@OnClick({R.id.tv_hello, R.id.iv_img})
@CheckNet
private void sayHello(){
Toast.makeText(this, tv_hello.getText().toString(), Toast.LENGTH_SHORT).show();
}
}
執行效果
有網狀態下,可以正常點選然後提示,無網狀態下提示網路的問題。
下面看看具體實現
我閱讀了xUtils和butterknife的原始碼,xUtils使用有點麻煩,butterknife的定義(變數,方法等)只能是public,所以我結合兩者,造出適合自己的,習慣是private定義,以及追求使用方便。
首先要了解一下反射機制,不懂的話百度一下
好。coding
屬性注入 : 利用反射去 獲取Annotation –> value –> findViewById –> 反射注入屬性
事件注入 :利用反射去 獲取Annotation –> value –> findViewById –> setOnclickListener –> 動態代理反射執行方法
新建一個Module,新增以下的類
View註解的Annotation
/**
* @creation_time: 2017/5/7
* @author: Vegen
* @e-mail: [email protected]
* @describe: View註解的Annotation
*/
//@Target(ElementType.FIELD) 代表Annotation的位置 FIELD代表屬性 TYPE類上 CONSTRUCTOR建構函式上 METHOD方法上面
@Target(ElementType.FIELD)
//@Retention(RetentionPolicy.CLASS) 什麼時候生效 CLASS代表編譯時 RUNTIME執行時 SOURCE原始碼資源
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
//@ViewById(R.id,xxx)
int value();
}
View輔助類
/**
* @creation_time: 2017/5/7
* @author: Vegen
* @e-mail: [email protected]
* @describe: View輔助類
*/
public class ViewFinder {
private Activity activity;
private View view;
public ViewFinder(Activity activity) {
this.activity = activity;
}
public ViewFinder(View view) {
this.view = view;
}
public View findViewById(int viewId){
return activity != null ? activity.findViewById(viewId) : view.findViewById(viewId);
}
}
View工具類
/**
* @creation_time: 2017/5/7
* @author: Vegen
* @e-mail: [email protected]
* @describe: View工具類
*/
public class ViewUtils {
public static void inject(Activity activity){
inject(new ViewFinder(activity), activity);
}
public static void inject(View view){
inject(new ViewFinder(view), view);
}
public static void inject(View view, Object object){
inject(new ViewFinder(view), object);
}
// 相容上面三個方法 object是反射需要執行的類
public static void inject(ViewFinder finder, Object object){
// 注入屬性
injectFile(finder, object);
// 注入事件
injectEvent(finder, object);
}
/**
* 事件注入
* @param finder
* @param object
*/
private static void injectEvent(ViewFinder finder, Object object) {
// 1、獲取類的所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
// 2、獲取OnClick裡面的value值
for (Method method : methods){
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null){
int[] viewIds = onClick.value();
for (int viewId : viewIds){
// 3、findViewById找到View
View view = finder.findViewById(viewId);
//拓展功能 檢測網路
boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
if (view != null){
// 4、setOnClickListener
view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet));
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener{
private Object mObject;
private Method mMethod;
private boolean mIsCheckNet;
public DeclaredOnClickListener(Method method, Object object, boolean isCheckNet) {
this.mMethod = method;
this.mObject = object;
this.mIsCheckNet = isCheckNet;
}
@Override
public void onClick(View view) {
//判斷是否需要檢測網路
if (mIsCheckNet){
//需要
if (!isNetConnected(view.getContext())){
Toast.makeText(view.getContext(), "請檢查網路,請稍後重試", Toast.LENGTH_SHORT).show();
return;
}
}
//點選會呼叫該方法
try {
// 所有方法都可以 包括私有公有
mMethod.setAccessible(true);
// 5、反射注入執行方法
mMethod.invoke(mObject, view);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
/**
* 檢測手機資料是否可用
*/
public static boolean isMobileNetConnected(Context context){
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
return cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isConnected();
}
/**
* 檢測WIFI是否開啟可用
*/
public static boolean isWifiNetConnected(Context context){
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
return cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected();
}
/**
* 檢測網路是否可用
*/
public static boolean isNetConnected(Context context){
return isMobileNetConnected(context) || isWifiNetConnected(context);
}
/**
* 注入屬性
* @param finder
* @param object
*/
private static void injectFile(ViewFinder finder, Object object) {
// 1、獲取類裡面的所有的屬性
Class<?> clazz = object.getClass();
// 獲取所有屬性包括私有和公有
Field[] fields = clazz.getDeclaredFields();
// 2、獲取ViewById裡面的value值
for (Field field : fields){
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null){
// 獲取註解裡面的id值
int viewId = viewById.value();
// 3、findViewById找到View
View view = finder.findViewById(viewId);
if (view != null) {
// 能夠注入所有的修飾符 private public
field.setAccessible(true);
// 4、動態注入找到的View
try {
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
View 事件註解的Annotation
/**
* @creation_time: 2017/5/7
* @author: Vegen
* @e-mail: [email protected]
* @describe: View 事件註解的Annotation
*/
//@Target(ElementType.FIELD) 代表Annotation的位置 FIELD代表屬性 TYPE類上 CONSTRUCTOR建構函式上 METHOD方法上面
@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.CLASS) 什麼時候生效 CLASS代表編譯時 RUNTIME執行時 SOURCE原始碼資源
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
//@ViewById(R.id,xxx)
int[] value();
}
View 網路狀態註解的Annotation
/**
* @creation_time: 2017/5/7
* @author: Vegen
* @e-mail: [email protected]
* @describe: View 網路狀態註解的Annotation
*/
//@Target(ElementType.FIELD) 代表Annotation的位置 FIELD代表屬性 TYPE類上 CONSTRUCTOR建構函式上 METHOD方法上面
@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.CLASS) 什麼時候生效 CLASS代表編譯時 RUNTIME執行時 SOURCE原始碼資源
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNet {
}
好,到此為止已經完成註解框架了,下面看看使用
MainActivity
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.tv_hello)
private TextView tv_hello;
@ViewById(R.id.iv_img)
private ImageView iv_img;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
tv_hello.setText("你好,IOC");
}
@OnClick({R.id.tv_hello, R.id.iv_img})
@CheckNet
//方法名隨意
private void sayHello(){
Toast.makeText(this, tv_hello.getText().toString(), Toast.LENGTH_SHORT).show();
}
}
佈局檔案activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.vegen.study.ioctest.MainActivity"
android:orientation="vertical">
<TextView
android:id="@+id/tv_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_img"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
別忘了新增網路訪問許可權
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
具體程式碼以及demo已經開源到我的github,歡迎star,有問題交流一下,地址:Vegen的github