http://blog.csdn.net/johnny901114/article/details/52662376
I. Overview
JakeWharton I want to be in Android No one knows, no one knows. ButterKnife This framework is from his own hands. This framework I believe many people have used, this series of blogs is to bring you a deeper understanding of the framework, ButterKnife has so far 1w + star:
It would be a pity if we were still in use for this excellent framework.
The main contents of this series are as follows:
1. What is ButterKnife?
2. The function and function of ButterKnife are introduced.
3. The implementation principle of ButterKnife.
4. Realize a ButterKnife by yourself.
Second, what is ButterKnife?
ButterKnife is a compile-time dependency injection framework for simplification android Template code like findViewById, setOnclickListener, etc.
For example, when writing an activity interface, there is often the following code:
public class MyActivity extents Activity{
private EditText etConsultValidDate;
private TextView tvToolbarCenter;
private TextView tvLeftAction;
private TextView tvRightAction;
private TextView tvConsultTip;
private TextView tvSuggestTime;
private EditText etConsultTitle;
private EditText etConsultDesc;
private EditText etConsultTime;
private EditText etConsultNumber;
private RelativeLayout rlContactInfo;
private LinearLayout llAnswerTime;
private TextView tvAnswerTime, tvAnswerTimePre;
private TextView tvToolbarRight;
private LinearLayout llBottom;
private int from;
private RelativeLayout rlOppositeInfo;
private ImageView ivHeadIcon;
private TextView tvOppositeUsername, tvOppositeDesc;
@Override
protected void initViews() {
tvExpertIdentify = (TextView) findViewById(R.id.tv_expert_identify);
llBottom = (LinearLayout) findViewById(R.id.ll_bottom);
rlOppositeInfo = (RelativeLayout) findViewById(R.id.rl_opposite_info);
ivHeadIcon = (ImageView) findViewById(R.id.iv_head_icon);
tvOppositeUsername = (TextView) findViewById(R.id.tv_opposite_username);
tvOppositeDesc = (TextView) findViewById(R.id.tv_opposite_desc);
rbOppositeScore = (RatingBar) findViewById(R.id.rbar_star);
tvUserCompany = (TextView) findViewById(R.id.tv_user_company);
infoArrow = findViewById(R.id.iv_member_info_arrow);
tvConsultTip = (TextView) findViewById(R.id.tv_consult_tip);
tvLeftAction = (TextView) findViewById(R.id.tv_left_action);
tvRightAction = (TextView) findViewById(R.id.tv_right_action);
llAnswerTime = (LinearLayout) findViewById(R.id.ll_answer_time);
tvAnswerTimePre = (TextView) findViewById(R.id.tv_answer_time_pre);
tvAnswerTime = (TextView) findViewById(R.id.tv_answer_time);
tvLeftAction.setOnClickListener(this);
tvRightAction.setOnClickListener(this);
etConsultTitle = (EditText) findViewById(R.id.et_consult_title);
etConsultDesc = (EditText) findViewById(R.id.et_consult_desc);
etConsultTime = (EditText) findViewById(R.id.et_contact_time);
etConsultNumber = (EditText) findViewById(R.id.et_contact_number);
etConsultValidDate = (EditText) findViewById(R.id.et_consult_valid_day);
tvSuggestTime = (TextView) findViewById(R.id.tv_contact_time);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
Initialize a large number of Views template code without technical content. If the interface is more complex, there will be more code like this. ButterKnife is a great way to simplify the lengthy code above.
public class MyActivity extents Activity{
@BindView(R.id.xxx) EditText etConsultValidDate;
@BindView(R.id.xxx) TextView tvToolbarCenter;
@BindView(R.id.xxx) TextView tvLeftAction;
@BindView(R.id.xxx) TextView tvRightAction;
@BindView(R.id.xxx) TextView tvConsultTip;
@BindView(R.id.xxx) TextView tvSuggestTime;
@BindView(R.id.xxx) EditText etConsultTitle;
@BindView(R.id.xxx) EditText etConsultDesc;
@BindView(R.id.xxx) EditText etConsultTime;
@BindView(R.id.xxx) EditText etConsultNumber;
@BindView(R.id.xxx) RelativeLayout rlContactInfo;
@BindView(R.id.xxx) LinearLayout llAnswerTime;
@BindView(R.id.xxx) TextView tvAnswerTime, tvAnswerTimePre;
@BindView(R.id.xxx) TextView tvToolbarRight;
@BindView(R.id.xxx) LinearLayout llBottom;
@BindView(R.id.xxx) RelativeLayout rlOppositeInfo;
@BindView(R.id.xxx) ImageView ivHeadIcon;
@BindView(R.id.xxx) TextView tvOppositeUsername;
@BindView(R.id.xxx) TextView tvOppositeDesc;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
//Initialize Views
ButterKnife.bind(this);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
In contrast, it greatly simplifies the initialization code of View.
3. Introduction to ButterKnife's Functions (All Functions)
In addition to the @BindView annotation above, there are other functions:
1. Initialize multiple Views with @BindViews
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List nameViews;
2. Use @OnClick to set up listening events
@OnClick(R.id.submit)
public void submit(View view) {
// TODO do something...
}
If you don't want submit method parameters, you can remove them as follows:
@OnClick(R.id.submit)
public void submit() {
// TODO do something...
}
View parameters can also be automatically converted, such as setting click events for TextView.
@OnClick(R.id.submit)
public void submit(TextView textView) {
// TODO do something...
}
If it is a custom View, you can not specify View Id, such as:
public class FancyButton extends Button {
@OnClick
public void onClick() {
// TODO do something!
}
}
3. listView item click event
@OnItemSelected(R.id.list_view)
void onItemSelected(int position) {
// TODO ...
}
4. onTouchEvent of view
@OnTouch(R.id.example) boolean onTouch() {
Toast.makeText(this, "Touched!", Toast.LENGTH_SHORT).show();
return false;
}
5. Listen for EditText's addTextChangedListener
@OnTextChanged(R.id.example) void onTextChanged(CharSequence text) {
Toast.makeText(this, "Text changed: " + text, Toast.LENGTH_SHORT).show();
}
6. Setting OnPage ChangeListener for ViewPager
@OnPageChange(R.id.example_pager) void onPageSelected(int position) {
Toast.makeText(this, "Selected " + position + "!", Toast.LENGTH_SHORT).show();
}
7. Set up OnEditor ActionListener for TextView (this event is mainly used to set buttons on the soft keyboard)
@OnEditorAction(R.id.example) boolean onEditorAction(KeyEvent key) {
Toast.makeText(this, "Pressed: " + key, Toast.LENGTH_SHORT).show();
return true;
}
8. Setting the OnFocusChangeListener event for View
@OnFocusChange(R.id.example) void onFocusChanged(boolean focused) {
Toast.makeText(this, focused ? "Gained focus" : "Lost focus", Toast.LENGTH_SHORT).show();
}
9. Setting View's OnLong ClickListener Long Press Event
@OnLongClick(R.id.example) boolean onLongClick() {
Toast.makeText(this, "Long clicked!", Toast.LENGTH_SHORT).show();
return true;
}
10. Binding of resources
@ BindString(R.string.title) String title; //string
@BindDrawable(R.drawable.graphic) Drawable graphic; //drawable
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
@ BindArray(R.array.countries) String[] countries; string array
@BindArray(R.array.icons) TypedArray icons;
@BindBool(R.bool.is_tablet) boolean isTablet;
4. The Realization Principle of ButterKnife
From the example above, we know that to use ButterKnife, we first need to use annotations in the target code, and then call ButterKnife.bind(this); method in the onCreate lifecycle method. There's nothing to say about using annotations, just look at ButterKnife.bind(this); how does this approach work?
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
Get the decorView of activity, and then call the createBinding method:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
//Get the bytecode of target
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
//Search constructors (what class constructors are analyzed below)
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
//Constructing class instances by reflection
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
//Ignore Exceptions
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
The first parameter target of the createBinding method is our activity instance, and source is decorView. The above code is also relatively simple, I also added comments. I won't say much about this method. Then see how the findBindingConstructorForClass method is implemented:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//Find the constructor from the container and return directly if found.
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
//If it's a class in the android framework, return it directly
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//Splice the class name and get the bytecode of that class
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
//noinspection unchecked
//Getting the construction method of this class
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
The core code of the findBindingConstructorForClass method is the following two lines of code:
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
- 1
- 2
- 1
- 2
This means that the target type (qualified name) splices _ViewBinding, and then obtains the construction method of the spliced class. So where does clsName +_ViewBinding come from?
We downloaded butterKnife's source code (version 8.4.0), built the code, and looked for the _ViewBinding ending Java Class, found that there are only ten, are located in the respective project build - > gernerated - > source - > apt - > debug directory.
Take SimpleActivity_ViewBinding as an example:
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;
public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
//ignore some code
@UiThread
public SimpleActivity_ViewBinding(final T target, View source) {
target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
//ignore some code
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
target is actually our activity above. source is DecorView. It was found that all the initial work of View was in the SimpleActivity_ViewBinding construction method.
// Generated code from Butter Knife. Do not modify!
- 1
- 1
From this we know that SimpleActivity_ViewBinding was generated by ButterKnife. So how does ButterKnife generate this class?
A corresponding class is generated through a tool called APT (Annotation Processing Tool).
In conclusion:
1. butterKnife is a runtime dependency injection framework that effectively simplifies some duplicate code.
2. butterKnife performs initialization by invoking the corresponding class constructor through reflection in the ButterKnife.bind method, so butterKnife does not use reflection at all, but only in this place. So butterKnife is also very efficient. Reflective technology should not be used at all or abused everywhere. Especially in android, there is reflection everywhere, which has a certain impact on performance.
3. butterknife uses apt Technology to generate java classes.
This series is expected to have three articles, and the next one will show how to use apt in android studio. The last article describes how to implement the initialization of view by calling the bind method.