Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Butterknife淺分析

Android Butterknife淺分析

編輯:關於Android編程

今天很順利的完成了公司的任務,干嘛呢,當然是寫寫代碼看看書了。

發現個問題,一個APP中很多次的使用了一段代碼,而且這行代碼還非常不好省略,這個就是findViewId()和onClick,一個app肯定有界面和按鈕,有見面就有控件,有控件就有點擊需求,而有這些需求和控件就必須要在Activity或者Fragment中使用這段findViewId()和onClick(),真是太煩了,現在github上大神那麼多,去看看有什麼好的解決辦法呗。

首先我發現了Android" target="_blank">thinkAndroid這個框架,這是個很好的框架,集成了很多的模塊:

MVC模塊:實現了視圖和模型的分離,當然不用說IOC模塊:這個就是我們所需要的,下面我們會認真的去看下這個模塊,他們的github上的介紹是說:完全注解方式就可以進行UI的綁定,res中的資源的讀取,以及對象的初始化。http模塊:通過httpclient進行封裝http數據請求,支持異步和同步方式加載。呃呃呃,這個什麼情況,現在的Android studio上已經不支持httpclient了,要與時俱進啊大神...
緩存模塊:通過簡單的配置及設計可以很好的實現緩存,對緩存可以隨意的配置圖片緩存模塊:imageview加載圖片的時候無需考慮圖片加載過程中出現的oom和android容器快速滑動時候出現的圖片錯位等現象。配置器模塊:可以對簡易的實現配對配置的操作,目前配置文件可以支持Preference、Properties對配置進行存取。日志打印模塊:可以較快的輕易的是實現日志打印,支持日志打印的擴展,目前支持對sdcard寫入本地打印、以及控制台打印下載器模塊:可以簡單的實現多線程下載、後台下載、斷點續傳、對下載進行控制、如開始、暫停、刪除等等。網絡狀態檢測模塊:當網絡狀態改變時,對其進行檢測。

模塊很多,上面這個東西是從他們的github上復制過來的,其他的我們不說,先看看所謂了ioc模塊:

又給出例子:

 

    @TAInject 
    Entity entity; //目前只能對無參構造函數進行初始化
    @@TAInject(id=R.string.app_name)
    String appNameString;
    @TAInjectResource(id=R.attr.test)
    int[] test; 
    @TAInjectView(id=R.id.add);
    Button addButton;
我們分析代碼可以看到這個框架是通過@TAInjectView來快速初始化控件的。

 

看下@TAInjectView的代碼:
 

package com.ta.annotation;

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 TAInjectView
{
	/** View的ID */
	public int id() default -1;

	/** View的單擊事件 */
	public String click() default "";

	/** View的長按鍵事件 */
	public String longClick() default "";

	/** View的焦點改變事件 */
	public String focuschange() default "";

	/** View的手機鍵盤事件 */
	public String key() default "";

	/** View的觸摸事件 */
	public String Touch() default "";
}

 

是一個注解類用RetentionPloicy.RUNTIME做修飾,而且制定了修飾的類別Field,在這裡看到大神很貼心的貼上了注釋,幾乎包含了控件的所有操作,很是方便,看下下面這行代碼:

 

package com.ta.util;

import java.lang.reflect.Field;

import com.ta.annotation.TAInject;
import com.ta.annotation.TAInjectResource;
import com.ta.annotation.TAInjectView;

import android.app.Activity;
import android.content.res.Resources;

public class TAInjector
{
	private static TAInjector instance;

	private TAInjector()
	{

	}

	public static TAInjector getInstance()
	{
		if (instance == null)
		{
			instance = new TAInjector();
		}
		return instance;
	}

	public void inJectAll(Activity activity)
	{
		// TODO Auto-generated method stub
		Field[] fields = activity.getClass().getDeclaredFields();
		if (fields != null && fields.length > 0)
		{
			for (Field field : fields)
			{
				if (field.isAnnotationPresent(TAInjectView.class))
				{
					injectView(activity, field);
				} else if (field.isAnnotationPresent(TAInjectResource.class))
				{
					injectResource(activity, field);
				} else if (field.isAnnotationPresent(TAInject.class))
				{
					inject(activity, field);
				}
			}
		}
	}

	private void inject(Activity activity, Field field)
	{
		// TODO Auto-generated method stub
		try
		{
			field.setAccessible(true);
			field.set(activity, field.getType().newInstance());
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	private void injectView(Activity activity, Field field)
	{
		// TODO Auto-generated method stub
		if (field.isAnnotationPresent(TAInjectView.class))
		{
			TAInjectView viewInject = field.getAnnotation(TAInjectView.class);
			int viewId = viewInject.id();
			try
			{
				field.setAccessible(true);
				field.set(activity, activity.findViewById(viewId));
			} catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	private void injectResource(Activity activity, Field field)
	{
		// TODO Auto-generated method stub
		if (field.isAnnotationPresent(TAInjectResource.class))
		{
			TAInjectResource resourceJect = field
					.getAnnotation(TAInjectResource.class);
			int resourceID = resourceJect.id();
			try
			{
				field.setAccessible(true);
				Resources resources = activity.getResources();
				String type = resources.getResourceTypeName(resourceID);
				if (type.equalsIgnoreCase("string"))
				{
					field.set(activity,
							activity.getResources().getString(resourceID));
				} else if (type.equalsIgnoreCase("drawable"))
				{
					field.set(activity,
							activity.getResources().getDrawable(resourceID));
				} else if (type.equalsIgnoreCase("layout"))
				{
					field.set(activity,
							activity.getResources().getLayout(resourceID));
				} else if (type.equalsIgnoreCase("array"))
				{
					if (field.getType().equals(int[].class))
					{
						field.set(activity, activity.getResources()
								.getIntArray(resourceID));
					} else if (field.getType().equals(String[].class))
					{
						field.set(activity, activity.getResources()
								.getStringArray(resourceID));
					} else
					{
						field.set(activity, activity.getResources()
								.getStringArray(resourceID));
					}

				} else if (type.equalsIgnoreCase("color"))
				{
					if (field.getType().equals(Integer.TYPE))
					{
						field.set(activity,
								activity.getResources().getColor(resourceID));
					} else
					{
						field.set(activity, activity.getResources()
								.getColorStateList(resourceID));
					}

				}
			} catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	public void inject(Activity activity)
	{
		// TODO Auto-generated method stub
		Field[] fields = activity.getClass().getDeclaredFields();
		if (fields != null && fields.length > 0)
		{
			for (Field field : fields)
			{
				if (field.isAnnotationPresent(TAInject.class))
				{
					inject(activity, field);
				}
			}
		}
	}

	public void injectView(Activity activity)
	{
		// TODO Auto-generated method stub
		Field[] fields = activity.getClass().getDeclaredFields();
		if (fields != null && fields.length > 0)
		{
			for (Field field : fields)
			{
				if (field.isAnnotationPresent(TAInjectView.class))
				{
					injectView(activity, field);
				}
			}
		}
	}

	public void injectResource(Activity activity)
	{
		// TODO Auto-generated method stub
		Field[] fields = activity.getClass().getDeclaredFields();
		if (fields != null && fields.length > 0)
		{
			for (Field field : fields)
			{
				if (field.isAnnotationPresent(TAInjectResource.class))
				{
					injectResource(activity, field);
				}
			}
		}
	}

}
這才是具體讓findViewId()消失的類,一模了然的類,是一個單例模式,裡面injectView,injectResource來實現具體的方法,代碼不是很難理解,我就不對說了。

 

這個框架其實很不錯的,但是看情況大神好像已經不更新這個框架了,只能放棄,看下代碼好了,學學大神思路也是進步。

上面已經把大神的github給出了,有興趣的可以點擊進去看看。

看完這個之後我又開始找已經在Android studio上更新了的框架,發現了這個:Butterknife來自JakeWharton大神。

看下這個框架的實現代碼:

 

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @BindString(R.string.login_error) String loginErrorMessage;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

看到這個我就想,這就是我要找的框架。

 

我們接下來去分析這個的實現方法看下這個框架的代碼結構:

\

額,很復雜的樣子,看下onBindView之類的類在哪,去看下,

\

我們在butterknife-annotations模塊找到了他們,分的非常仔細,看到名字就可以知道這個類是做什麼的。我們首先找個典型的例子看下,BindView,上面的代碼中表示這個類主要的是初始化控件的,我們看下這個代碼:

 

package butterknife;

import android.support.annotation.IdRes;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.CLASS;

/**
 * Bind a field to the view for the specified ID. The view will automatically be cast to the field
 * type.
 * 
* {@literal @}BindView(R.id.title) TextView title; * 
*/ @Retention(CLASS) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); }
不是很難哦,設置的保留策略為Class,注解用於Field上。傳入一個IdRes,並且直接以value的形式進行設置。看下注解處理器的實現:

 

 


package butterknife.compiler; @AutoService(Processor.class) public final class ButterKnifeProcessor extends AbstractProcessor { static final Id NO_ID = new Id(-1); static final String VIEW_TYPE = "android.view.View"; private static final String COLOR_STATE_LIST_TYPE = "android.content.res.ColorStateList"; private static final String BITMAP_TYPE = "android.graphics.Bitmap"; private static final String DRAWABLE_TYPE = "android.graphics.drawable.Drawable"; private static final String TYPED_ARRAY_TYPE = "android.content.res.TypedArray"; private static final String NULLABLE_ANNOTATION_NAME = "Nullable"; private static final String STRING_TYPE = "java.lang.String"; private static final String LIST_TYPE = List.class.getCanonicalName(); private static final String R = "R"; private static final List> LISTENERS = Arrays.asList(// OnCheckedChanged.class, // OnClick.class, // OnEditorAction.class, // OnFocusChange.class, // OnItemClick.class, // OnItemLongClick.class, // OnItemSelected.class, // OnLongClick.class, // OnPageChange.class, // OnTextChanged.class, // OnTouch.class // ); private static final List SUPPORTED_TYPES = Arrays.asList( "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string" ); private Elements elementUtils; private Types typeUtils; private Filer filer; private Trees trees; private final Map symbols = new LinkedHashMap<>(); @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); trees = Trees.instance(processingEnv); } @Override public Set getSupportedAnnotationTypes() { Set types = new LinkedHashSet<>(); for (Classannotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } private Set> getSupportedAnnotations() { Set> annotations = new LinkedHashSet<>(); annotations.add(BindArray.class); annotations.add(BindBitmap.class); annotations.add(BindBool.class); annotations.add(BindColor.class); annotations.add(BindDimen.class); annotations.add(BindDrawable.class); annotations.add(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; } @Override public boolean process(Setelements, RoundEnvironment env) { //BindingClass類對象,代表生成代理類的信息 //Map<>集合代理類對象集合 Map targetClassMap = findAndParseTargets(env); //生成代理類 for (Map.Entry entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); for (JavaFile javaFile : bindingClass.brewJava()) { try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write view binder for type %s: %s", typeElement, e.getMessage()); } } } return true; } private Map findAndParseTargets(RoundEnvironment env) { Map targetClassMap = new LinkedHashMap<>(); Set erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); //通過getElementsAnnotatedWith拿到我們注解的每個元素,返回值為Map<>集合 for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceArray(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.class, e); } } //代碼省略 return targetClassMap; } //代碼省略 } 



代碼很長只留核心代碼,其他省略

嗯哼,一般的寫法,繼承了AbstractProcessor,並實現了public synchronized void init(ProcessingEnvironment env),@Override public Set getSupportedAnnotationTypes() 和private Set> getSupportedAnnotations()函數,在這些函數裡,主要是將BindView之類的注解和注解處理器連接,一般情況下,這些函數的實現是通用的,主要是返回注解類型,返回源碼版本,和初始化輔助類。處理器中還有一個非常重要的函數需要我們的實現:process(),這是核心,在我們的知道的process()函數的實現復雜有簡單,其實就實現了兩個功能而已:

 

收集代理類信息使用的是getElementsAnnotatedWith函數 。返回代理類集合。
生成咱們在使用BindView之後編譯生成的代理類,想上面的例子,在MainActivity中的,我們會生成一個MainActivity_ViewBinder和MainActivity_ViewBining代理類,在Android studio的build文件夾下可以很清楚的看到這個類的存在.

上面的BindingClass類為生成java類的方法,通過收集到的信息,拼接完成代理類對象。

完成代理類對象之後,提供一個api供用戶調用,ButterKnife的實現API為ButterKnife類,看下代碼:

 

package butterknife;

public final class ButterKnife {

  //代碼省略
  //傳入當前對象,不管是Activity,fragment或者dialog全都行,尋找代理類
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    return getViewBinder(target).bind(Finder.ACTIVITY, target, target);
  }

 //代碼省略
  //強制轉換接口為統一接口,並調用接口提供的方法。
  @NonNull @CheckResult @UiThread
  static ViewBinder

這個類中主要做了兩件事情:

 

傳入當前對象,不管是Activity,fragment或者dialog全都行,尋找我們剛才生成的代理類強制轉換接口為統一接口,並調用接口提供的方法。

至此,一個簡化版的findViewById就算是完成了,可以省略代碼寫控件初始化了。。。。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved