A tutorial on the use of control reversal IOC design patterns in Android application development _android

Source: Internet
Author: User
Tags eventbase object object reflection throwable

1, overview
First, let's brag, what is the IOC, control reversal (inversion, English abbreviation for IOC), what do you mean?
is that you need to use a lot of member variables within a class, the traditional way of writing, you want to use these member variables, then you will come out with a Bai ~ ~
IOC principle is: No, we do not new, so the coupling is too high; you configure an XML file, which identifies which class, Which member variables to use, waiting to load this class, I help you inject (new) in;
What is the benefit of doing this?
  Answer this question, just can answer another question, many people ask, the project layered development is, divided into control layer, business layer, DAO layer God Horse. Then each layer for the sub to a packet-drop interface, a package to implement it? As long as a implementation of the package can not ~ Just, if you understand the IOC, you know the function of these interfaces, it's not that you don't need new, you just declare a member variable + write a configuration file, someone help you new; At this point, you can declare the member variables you need to use in the class as interfaces. Then you'll find out what you need to do when the implementation class changes, or if you switch the implementation class. You just have to make a simple change in the configuration file. If you're using a real implementation class, to change the implementation class now, you need to find all the declarations of this implementation class, manually modify the class name; If you meet a changeable boss, yes, hehe ~
  Of course, many will feel, write a configuration file, horizontal slot, this much trouble. As a then, there is another scheme, you have a busy configuration file trouble, you use the annotation bar. You add an annotation to the member variable you need to inject, such as: @Inject, so that you can not say such a word trouble ~ ~
  Of course, with configuration files and annotations, so how to inject it? is actually to turn a string-like path into a class? Of course, reflected in the field, said, a long time ago, the reflection is very slow ah, well, it was a long time ago, now is not too slow, of course, certainly not up to the original speed ~ ~ No reflection, no frame.
  If you feel annotations, reflect the good advanced of God horse. I say: Just do It, you will find annotations and you write a common javabean almost; reflection? API just a few lines, do not be intimidated ~

2. Framework Implementation
got to get to the point, the Android IOC framework, in fact, is to help you inject all the controls, layout files or whatever. If you have used the framework of the Xutils,afinal class, you are certainly not unfamiliar ~
Inject view
Let's say we have an activity with 10 or so view inside.
Traditional approach: We need to first give this activity to set the layout file, and then in the OnCreate inside a Findviewbyid
Objective approach: Add an annotation to the activity class to help us automatically inject the layout liberal arts; When you declare view, add a line of annotations, and then automatically help us Findviewbyid;
And so our target class is this:

@ContentView (value = r.layout.activity_main) public 
class Mainactivity extends baseactivity 
{ 
 @ Viewinject (R.ID.ID_BTN) 
 private Button mBtn1; 
 @ViewInject (R.ID.ID_BTN02) 
 private Button mBtn2; 

3, coding
(1) Define annotations
First we need two annotation files:

Package com.zhy.ioc.view.annotation; 
 
Import Java.lang.annotation.ElementType; 
Import java.lang.annotation.Retention; 
Import Java.lang.annotation.RetentionPolicy; 
Import Java.lang.annotation.Target; 
 
@Target (Elementtype.type) 
@Retention (retentionpolicy.runtime) public 
@interface Contentview 
{ 
 int value (); 
} 

Contentview is used on classes, primarily to indicate the layout files that the activity needs to use.

 @ContentView (value = r.layout.activity_main) public 
class Mainactivity 

 
Package com.zhy.ioc.view.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 Viewinject 
{ 
 int value (); 
} 

Used on a member variable to specify the ID of the view

@ViewInject (R.ID.ID_BTN) 
 private Button mBtn1; 

A simple remark: define the keyword @interface; The @Target represents where the annotation can be used, possible types type (Class), FIELD (member variable), possible types:

public enum ElementType { 
 /** 
  * Class, interface or enum declaration. 
 */TYPE, 
 /** 
  * Field declaration. 
 * * FIELD,/** * method 
  declaration. 
 * * method,/** 
  * Parameter declaration. 
 * * PARAMETER,/** 
  * constructor declaration. 
 * * constructor,/** * Local 
  variable declaration. 
 * * local_variable,/** 
  * Annotation type declaration. 
 * * annotation_type,/** 
  * Package declaration. 
  * * 
 PACKAGE 
} 

is the enumeration.
@Retention: Represents the level at which the annotation information needs to be saved; We set this to run time.
Possible types:

public enum Retentionpolicy { 
 /** 
  * Annotation was only available in the source code. 
  */ 
 source, 
 /** 
  * Annotation is available in the SOURCE code and in the class file, but not * at 
  runtime. T His is the default policy. 
  / 
 class, 
 /** 
  * Annotation is available in the source code, the class file and are 
  * available at Runtim E. 
  * * 
 RUNTIME 
} 

These enumerations ~

(2) Mainactivity

Package com.zhy.zhy_xutils_test; 
Import android.app.Activity; 
Import Android.os.Bundle; 
Import Android.view.View; 
Import Android.view.View.OnClickListener; 
Import Android.widget.Button; 
 
Import Android.widget.Toast; 
Import Com.zhy.ioc.view.ViewInjectUtils; 
Import Com.zhy.ioc.view.annotation.ContentView; 
 
Import Com.zhy.ioc.view.annotation.ViewInject; @ContentView (value = r.layout.activity_main) public class Mainactivity extends activity implements Onclicklistener {@ 
 Viewinject (r.id.id_btn) private Button mBtn1; 
 
 @ViewInject (R.ID.ID_BTN02) private Button mBtn2; 
   
  @Override protected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate); 
 
  Viewinjectutils.inject (this); 
  Mbtn1.setonclicklistener (this); 
 Mbtn2.setonclicklistener (this); @Override public void OnClick (View v) {switch (V.getid ()) {Case R.id.id_btn:toast.maketext (MainA Ctivity.this, "Why Do you click me?", Toast.length_short). show (); 
 
  Break 
   Case R.id.id_btn02:toast.maketext (mainactivity.this, "I AM sleeping!!!", Toast.length_short). Show (); 
  Break 
 } 
 } 
 
}

The annotations are all written, the core code is viewinjectutils.inject (this) ~

(3) Viewinjectutils
A, first is the code that injects the primary layout file:

/** 
  * Inject Master layout file 
  * 
  * @param activity 
 /private static void Injectcontentview (activity activity) 
 { 
  class< extends activity> clazz = Activity.getclass (); 
  Whether there is a contentview annotation on the query class 
  Contentview Contentview = clazz.getannotation (contentview.class); 
  if (Contentview!= null)//exists 
  { 
   int contentviewlayoutid = Contentview.value (); 
   Try 
   {Method Method 
    = Clazz.getmethod (Method_set_contentview, 
      int.class); 
    Method.setaccessible (true); 
    Method.invoke (activity, Contentviewlayoutid); 
   } catch (Exception e) 
   { 
    e.printstacktrace (); 
   } 
  } 
 } 

Through the incoming activity object, get its class type, judge whether write Contentview this annotation, if writes, reads its value, then obtains Setcontentview this method, uses invokes to make the call;
There is a constant:

private static final String Method_set_contentview = "Setcontentview"; 

B, next is to inject views

 private static final String method_find_view_by_id =" Findviewbyid "; /** * Inject All controls * * @param activity/private static void injectviews (activity activity) {class< ex 
  tends activity> clazz = Activity.getclass (); 
  field[] fields = Clazz.getdeclaredfields (); Iterate through all member variables for (field field:fields) {viewinject viewinjectannotation = field. Getannotation (Viewi 
   Nject.class); 
    if (viewinjectannotation!= null) {int viewid = Viewinjectannotation.value (); 
     if (Viewid!=-1) {LOG.E ("TAG", viewid+ ""); 
      Initialize VIEW try {method method = Clazz.getmethod (method_find_view_by_id, Int.class); 
      Object Resview = Method.invoke (activity, VIEWID); 
      Field.setaccessible (TRUE); 
     Field.set (activity, Resview); 
     catch (Exception e) {e.printstacktrace (); } 
 
    } 
   } 
 
  } 
 
 } 

Gets all the declared attributes, traverses, finds the attribute that exists the viewinject annotation, or its value, then calls the Findviewbyid method, and finally sets the value to the field~~~
Well, it's good to write these two methods in the inject.

 public static void inject (activity activity) 
 { 
   
  Injectcontentview (activity); 
  Injectviews (activity); 
   
 } 

Effect Chart:

The injection of 4.View events
The light has the view injection to be able to do, we write the view the goal, many is uses for the interaction, may have to click the god horse's bar. Abandon the traditional God horse, setonclicklistener, and then implement anonymous classes or other ways of God horse, we change to:

Package com.zhy.zhy_xutils_test; 
 
Import Android.view.View; 
Import Android.widget.Button; 
Import Android.widget.Toast; 
 
Import Com.zhy.ioc.view.annotation.ContentView; 
Import Com.zhy.ioc.view.annotation.OnClick; 
Import Com.zhy.ioc.view.annotation.ViewInject; 
 
@ContentView (value = r.layout.activity_main) public 
class Mainactivity extends baseactivity 
{ 
 @ Viewinject (R.ID.ID_BTN) 
 private Button mBtn1; 
 @ViewInject (R.ID.ID_BTN02) 
 private Button mBtn2; 
 
 @OnClick ({r.id.id_btn, r.id.id_btn02}) public 
 void clickbtninvoked (view view) 
 { 
  switch (View.getid () 
  {case 
  r.id.id_btn: 
   toast.maketext (This, "inject Btn01!", Toast.length_short). Show (); 
   break; 
  Case R.ID.ID_BTN02: 
   toast.maketext (This, "inject Btn02!", Toast.length_short). Show (); 
   break; 
  }}} 
 
 

The injection of events from 1 or more controls is done directly by adding annotations to any one of the methods in the activity. Here I moved the oncreate to the baseactivity, which called viewinjectutils.inject (this);

(1) Note file

Package com.zhy.ioc.view.annotation; 
 
Import Java.lang.annotation.ElementType; 
Import java.lang.annotation.Retention; 
Import Java.lang.annotation.RetentionPolicy; 
Import Java.lang.annotation.Target; 
 
@Target (Elementtype.annotation_type) 
@Retention (retentionpolicy.runtime) public 
@interface Eventbase 
{ 
 class<?> listenertype (); 
 
 String Listenersetter (); 
 
 String methodname (); 
} 

 
Package com.zhy.ioc.view.annotation; 
 
Import Java.lang.annotation.ElementType; 
Import java.lang.annotation.Retention; 
Import Java.lang.annotation.RetentionPolicy; 
Import Java.lang.annotation.Target; 
 
Import Android.view.View; 
 
@Target (Elementtype.method) 
@Retention (retentionpolicy.runtime) 
@EventBase (Listenertype = View.OnClickListener.class, Listenersetter = "Setonclicklistener", methodname = "OnClick") public 
@interface OnClick 
{ 
 int[] value (); 
} 

Eventbase is mainly used to add annotations to the onclick annotation, after all, a lot of events, and set the name of the listener, the type of listener, the calling method names are fixed, corresponding to the above code:

Copy Code code as follows:

Listenertype = View.OnClickListener.class, Listenersetter = "Setonclicklistener", methodname = "OnClick"

The onclick is used to write on a method of an activity:
 @OnClick ({r.id.id_btn, r.id.id_btn02}) public 
 void clickbtninvoked (view view) 

If you remember, last blog our viewinjectutils.inject (this), there are already two methods, this article one more:

public static void inject (activity activity) 
 { 
  Injectcontentview (activity); 
  Injectviews (activity); 
  Injectevents (activity); 
 } 

(2) Injectevents

/** * injected into all events * * @param activity/private static void Injectevents (event activity) {CLASS&L t;? 
  Extends activity> clazz = Activity.getclass (); 
  Method[] methods = Clazz.getmethods (); 
   Iterate through all the methods for (method Method:methods) {annotation[] annotations = method.getannotations (); Get all the annotations on the method for (Annotation annotation:annotations) {class<? extends annotation> Annotationtype = 
    annotation. Annotationtype (); 
    Get the annotation on the note eventbase eventbaseannotation = Annotationtype. Getannotation (Eventbase.class);  If set to Eventbase if (eventbaseannotation!= null) {//Remove set listener name, listener type, method name String Listenersetter 
     = Eventbaseannotation. Listenersetter (); 
     class<?> Listenertype = Eventbaseannotation.listenertype (); 
 
     String methodname = Eventbaseannotation.methodname (); 
     try {//Get the value method in the onclick annotation Amethod = Annotationtype   . Getdeclaredmethod ("value"); 
      Remove all Viewid int[] Viewids = (int[)) Amethod. Invoke (annotation, null); 
      Set up proxy Dynamichandler handler = new Dynamichandler (activity) via Invocationhandler; 
      Handler.addmethod (methodname, method); Object listener = proxy.newproxyinstance (Listenertype.getclassloader (), new class<?>[] {listener 
      Type}, handler); 
       Iterate through all the view, setting event for (int viewid:viewids) {View view = Activity.findviewbyid (Viewid); 
       Method Seteventlistenermethod = View.getclass (). GetMethod (Listenersetter, Listenertype); 
      Seteventlistenermethod.invoke (view, listener); 
     } catch (Exception e) {e.printstacktrace (); 

 } 
    } 
 
   } 
  } 
 
 }

Well, the annotation is as detailed as possible, the main is to traverse all the methods, get the method of the onclick annotation, and then get the annotation on the eventbase annotation, get the event to listen to the need to call the method name, type, and the name of the method to call The proxy object of the listener is obtained by proxy and Invocationhandler, the method is displayed, and the listener is set up by reflection.
Here is a difficult point, is about the appearance of Dynamichandler and proxy, if do not understand all right, later will explain in detail.

(3) Dynamichandler
here a class Dynamichandler is used, which is the implementation class of Invocationhandler:

Package Com.zhy.ioc.view; 
Import java.lang.ref.WeakReference; 
Import Java.lang.reflect.InvocationHandler; 
Import Java.lang.reflect.Method; 
 
Import Java.util.HashMap; 
 public class Dynamichandler implements Invocationhandler {private weakreference<object> handlerref; 
 
 Private final hashmap<string, method> methodmap = new hashmap<string, method> (1); 
 Public Dynamichandler (Object handler) {this.handlerref = new weakreference<object> (handler); 
 public void Addmethod (String name, method method) {Methodmap.put (name, method); 
 Public Object GetHandler () {return handlerref.get (); 
 The public void SetHandler (Object handler) {this.handlerref = new weakreference<object> (handler); @Override public Object Invoke (object proxy, Method method, object[] args) throws Throwable {object hand 
  Ler = Handlerref.get (); 
   if (handler!= null) {String methodname = Method.getname (); Method= Methodmap.get (methodname); 
   if (method!= null) {return Method.invoke (handler, args); 
 } return null; 
 } 
}

Well, the code is so much, so we realize that our events are injected ~ ~
Effect Chart:

The effect of the picture is not sprinkled good stickers, are the same ~ ~ ~
(3) about the agent
So, this article is over, no ~ ~ ~ about the following lines of code, I believe you must be confused, these lines do what?

Set up proxy       

Dynamichandler handler = new Dynamichandler (activity) via Invocationhandler; 
      Handler.addmethod (methodname, method); 
      Object listener = proxy.newproxyinstance ( 
        listenertype.getclassloader (), 
        new class<?>[] {Listenertype }, Handler); 

Invocationhandler and proxy in pairs appear, I believe that if you are familiar with Java, will certainly think of the dynamic Java Agent ~ ~ ~
on Invocationhandler and proxy articles, you can refer to: http:/ /WWW.IBM.COM/DEVELOPERWORKS/CN/JAVA/J-LO-PROXY1/PS:IBM's technical article is still quite good, after all, there is a review of the bonus ~
But our implementation has a certain difference, why do I say we doubt it, For example, reflection implementation:
Mbtn2.setonclicklistener (this); Where is the difficulty in such a code?
A, mBtn2 access? So easy
B, call Setonclicklistener? So easy to
but, this is, this is the Onclicklistener implementation class instance, Onclicklistener is an interface ~ ~ How do you complete the implementation class, heard of the reflection Newinstance object, but you are now interface!
Yes, now you know what these lines of code do? A proxy object for the interface is implemented, and the invocation method of the interface is processed in the invoke of the proxy class.
(4) code is the best teacher
Light said no one can understand, what are you in this xx?? Looking at the code, we simulate the implementation of a scenario where a Button,button is implemented in the
main class with two methods, a setonclicklistener and onclick, when the button's onclick is invoked, The triggered event is the click Method
in the main class that involves 4 classes:
Button

Package Com.zhy.invocationhandler; 
 
public class Button 
{ 
 private onclicklistener listener; 
 
 public void Setonclicklisntener (Onclicklistener listener) 
 { 
 
  This.listener = listener; 
 } 
 
 public void Click () 
 { 
  if (listener!= null) 
  { 
   Listener.onclick () 
}}} 

Onclicklistener interface

Package Com.zhy.invocationhandler; 
 
Public interface Onclicklistener 
{ 
 void OnClick (); 
} 

Onclicklistenerhandler, Invocationhandler implementation class

Package Com.zhy.invocationhandler; 
 
Import Java.lang.reflect.InvocationHandler; 
Import Java.lang.reflect.Method; 
Import Java.util.HashMap; 
Import Java.util.Map; 
 
public class Onclicklistenerhandler implements Invocationhandler 
{ 
 private Object targetobject; 
 
 Public Onclicklistenerhandler (Object object) 
 { 
  This.targetobject = object; 
 } 
 
 Private Map<string, method> methods = new hashmap<string, method> (); 
 
 public void Addmethod (String methodname, method method) 
 { 
  Methods.put (methodname, method); 
 } 
 
 @Override Public 
 object Invoke (Object proxy, Method method, object[] args) 
   throws Throwable 
 { 
 
  String methodname = Method.getname (); 
  Method Realmethod = Methods.get (methodname); 
  Return Realmethod.invoke (TargetObject, args); 
 } 
 
 

Our main

Package Com.zhy.invocationhandler; 
Import java.lang.reflect.InvocationTargetException; 
Import Java.lang.reflect.Method; 
 
Import Java.lang.reflect.Proxy; 
  
 public class Main {private button button = New button (); Public Main () throws SecurityException, IllegalArgumentException, Nosuchmethodexception, Illegalaccessexception, 
 invocationtargetexception {init (); 
 public void Click () {System.out.println ("button clicked!"); public void Init () throws SecurityException, Nosuchmethodexception, IllegalArgumentException, Illegalaccesse 
  Xception, invocationtargetexception {onclicklistenerhandler h = new Onclicklistenerhandler (this); 
  Method method = Main.class.getMethod ("click", null); 
  H.addmethod ("OnClick", method); Object clickproxy = proxy.newproxyinstance (OnClickListener.class.getClassLoader (), new class<?>[] {ONCLI 
  Cklistener.class}, h); 
 Method Clickmethod = Button.getclass (). GetMethod ("Setonclicklisntener",   Onclicklistener.class); 
   
 Clickmethod.invoke (button, clickproxy); public static void Main (string[] args) throws SecurityException, IllegalArgumentException, nosuchmethodexception 
   
  , Illegalaccessexception, invocationtargetexception {main main = new main (); 
 Main.button.click (); 

 } 
 
}

We simulate the button click: Call Main.button.click (), the actual execution is the main click method.
Looking at Init, we first initialize a onclicklistenerhandler, pass in the current instance of main, then get the main click Method and add it to the map in Onclicklistenerhandler.
And then through the proxy.newproxyinstance to get Onclicklistener This interface of an agent, so that all the methods of executing this interface, will invoke the Onclicklistenerhandler invoke method.
But what? After all, Onclicklistener is an interface, there is no method body ~ ~ Then do? This is the time to reach the map in our Onclicklistenerhandler:

@Override Public
 object Invoke (Object proxy, Method method, object[] args)
 throws Throwable
 {
 String methodname = Method.getname ();
 Method Realmethod = Methods.get (methodname);
 Return Realmethod.invoke (TargetObject, args);
 }

We show that the method to be executed, through the key value to the map inside, and so called to invoke, in fact, by passing the method name, the map stored in the method, and then call our preset method ~.
In this way, it should be understood that it is through proxy to get an agent of the interface, and then in the Invocationhandler using a map preset method to achieve the button's onclick, and main click Association.
Now look at the code in our injectevents:

Set up proxy 
      Dynamichandler handler = new Dynamichandler (activity) via Invocationhandler; 
      Add methods to map 
      Handler.addmethod (methodname, method); 
      Object listener = proxy.newproxyinstance ( 
        listenertype.getclassloader (), 
        new class<?>[] {Listenertype }, Handler); 

Is it the same as our init?
Well, we've explained how to relate the callback of the interface to the methods in our activity.

Note: Some of the code reference xutils this framework, after all, want to be perfect to achieve a complete injection is not one or two blog can be done, but the core and skeleton has been realized ~ ~ We are interested can continue to improve ~

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.