Explain the four why_android in Android XML that refer to custom internal Class View

Source: Internet
Author: User
Tags documentation lowercase static class tag name

I came across a custom view in XML that was defined in the form of an inner class, resulting in some pits. Although by reading some of the predecessors wrote the article to solve the problem, but I see a few of them are not completely clear why, so decided to do this summary.

Rules for using custom internal Class View

This article is mainly to summarize why, so first of all in the XML layout file referencing the internal class custom view of the practice, there are four points:

1. A custom class must be a static class;

2. Use view as tag in the XML file, note that V is a lowercase letter, small letter V, small Letter V;

3. Add Class attribute, note that there is no Android: namespace, indicating the full path of the custom view, and that the external class is connected to the internal class in dollar "$" instead of ".", note that you want US $ "$", do not "." ;

4. A custom view should contain at least a constructor with context, attributeset these two parameters

Layout Load Process main code

First of all, XML layout file loading is implemented using Layoutinflater, through the analysis of this article, we know that the actual use of the Layoutinflater class is its subclass Phonelayoutinflater, and then through the analysis of this article, We know that the key entry function for the real instantiation of the view is to createviewfromtag this function and then actually instantiate the view by CreateView, and here is the main code for the key function that the process uses:

Final object[] Mconstructorargs = new object[2];
      
  Static final class<?>[] Mconstructorsignature = new class[] {context.class, attributeset.class};
   .../** * Creates a view from a tag name using the supplied attribute set.
   * <p> * <strong>Note:</strong> Default visibility so the bridgeinflater can * override it. * @param parent of the parent view, used to inflate layout params * @param name "The XML tag used to Defin  E The view * @param context of the inflation context for the view, typically the * {@code parent} or base layout Inflater context * @param attrs the "attribute set for" XML tag used to define the view * @param ignorethemeattr            {@code true} to ignore the {@code android:theme} * attributes (if set) for the view being inflated, * {@code false} otherwise/view Createviewfromtag (view parent, String name, context, AttributeSet attr S, BooLean ignorethemeattr) {//** critical 1**//if (Name.equals ("View")) {name = Attrs.getattributevalue (NULL, "class")
    ;
    //Apply A theme wrapper, if allowed and one is specified.
      if (!ignorethemeattr) {final TypedArray ta = context.obtainstyledattributes (attrs, attrs_theme);
      Final int themeresid = Ta.getresourceid (0, 0);
      if (themeresid!= 0) {context = new Contextthemewrapper (context, THEMERESID);
    } ta.recycle ();
      } if (Name.equals (tag_1995)) {//Let's party like it ' s 1995!
    Return to new Blinklayout (context, attrs);
      try {view view;
      if (MFactory2!= null) {view = Mfactory2.oncreateview (parent, name, context, attrs);
      else if (mfactory!= null) {view = Mfactory.oncreateview (name, context, attrs);
      else {view = null; } if (view = = null && mprivatefactory!= null) {view = Mprivatefactory.oncreateview (parent, name, ContexT, Attrs);
        } if (view = = null) {final Object lastcontext = mconstructorargs[0];
        Mconstructorargs[0] = context; try {if ( -1 = Name.indexof ('. '))
          {//** key 2**//view = Oncreateview (parent, name, Attrs);
          else {//** key 3**//view = CreateView (name, null, ATTRS);
        }} finally {mconstructorargs[0] = Lastcontext;
    } return view; //Back is catch, omit} Protected View Oncreateview (view parent, String name, AttributeSet attrs) throws Class
  notfoundexception {return Oncreateview (name, attrs); Protected View Oncreateview (String name, AttributeSet attrs) throws ClassNotFoundException {return creat
  Eview (Name, "Android.view.", attrs); }/** * Low-level function for instantiating a view by name.
This is attempts to * instantiate a view class of the given <var>name</var> found in this   * layoutinflater ' s ClassLoader.
   * * @param name the full name of the class to be instantiated.
   * @param attrs The XML attributes supplied for this instance.
   * * @return View the newly instantiated view, or null. * * Public final View CreateView (string name, string prefix, AttributeSet attrs) throws ClassNotFoundException, INF
    lateexception {constructor< extends view> constructor = sconstructormap.get (name); class<?

    Extends view> clazz = null;

      try {trace.tracebegin (Trace.trace_tag_view, name); if (constructor = = null) {//** critical 4**////Class not found in the cache, if it's real, and try to add I T clazz = Mcontext.getclassloader (). loadclass (prefix!= null?)
        
        (prefix + name): name). Assubclass (View.class);
          if (mfilter!= null && clazz!= null) {Boolean allowed = Mfilter.onloadclass (clazz); if (!allowed) {failnotallowed (name, prefix, attrs);
        } constructor = Clazz.getconstructor (mconstructorsignature);
        Constructor.setaccessible (TRUE);
      Sconstructormap.put (name, constructor); else {//If we have a filter, apply it to cached constructor if (Mfilter!= null) {//have W
          E seen this name before?
          Boolean allowedstate = mfiltermap.get (name); if (allowedstate = = null) {//New class--Remember whether it is allowed clazz = Mcontext.getcla Ssloader (). loadclass (prefix!= null?
            
            (prefix + name): name). Assubclass (View.class);
            Boolean allowed = Clazz!= null && mfilter.onloadclass (clazz);
            Mfiltermap.put (name, allowed);
            if (!allowed) {failnotallowed (name, prefix, attrs);
          } else if (Allowedstate.equals (Boolean.false)) {failnotallowed (name, prefix, attrs); }
        }
      } object[] args = Mconstructorargs;
      ARGS[1] = attrs;
      * * Key 5**//final View view = Constructor.newinstance (args);
        if (view instanceof viewstub) {//Use the same context when inflating viewstub later.
        Final Viewstub viewstub = (viewstub) view;
      Viewstub.setlayoutinflater (Cloneincontext (context) args[0]);

    } return view;
 //The following are catch and finally processing, omitting}

The main code used in the

Phonelayoutinflater:

public class Phonelayoutinflater extends Layoutinflater { 
  private static final string[] Sclassprefixlist = { 
    "and Roid.widget. ", 
    " Android.webkit. ", 
    " Android.app. " 
  }; 
  //......
  
  /** Override Oncreateview to instantiate names this correspond to the widgets of the 
    Widget known. If we don ' t find a match, call through to our 
    super class. 
  * 
  /@Override protected View Oncreateview (String name, AttributeSet attrs) throws ClassNotFoundException { 
    //* * Key 6**//for
    (String prefix:sclassprefixlist) { 
      try { 
        View view = CreateView (name, prefix, attrs); 
        if (view!= null) {return 
          view; 
        } 
      catch (ClassNotFoundException e) {*/In the This case 
        we want to let The base class take a crack 
        /at it. 
      } 
    } 
 
    Return Super.oncreateview (name, attrs); 

  //.........
}

WHY

For any element in XML, start with "1" ("1", which means "key 1" in the code, followed by), followed by the process of the code:

1. "1" Where the name is "view" of the judgment, then there will be two cases, we first assume the use of "view"

2. Because the "1" satisfies the condition, name is assigned the value of the class attribute, such as "Com.willhua.view.MyView" or "Com.willhua.view.myclass$myview". In fact, this is also to use "view" to define, but not other names to define the reason.

3. According to the value of name, Name contains '. ' Symbol, so the code goes to "3", calls CreateView, and the prefix argument is empty

4. Come to "4", prefix is empty, so the LoadClass function parameter is name, that is, the value of the class attribute in A.2. We know that the argument passed to LoadClass is the class name of the class you want to load, whereas in Java, the class name representation of the inner class is appended to the Outer class class name with the symbol "$" to the inner class name, so the 3rd answer at the beginning of the article is here. To add, why do you come to "4" instead of the corresponding else block? When the class is first loaded, the constructor must not exist, that is, the if condition must be set up. Then wait until the back of the instantiation, it came to the else block, and in else fast just based on Mfilter do some can load the view of the judge, and does not affect the view of the load process.

5. Another important place in "4" is constructor = Class.getconstructor (mconstructorsignature). First, the definition of mconstructorsignature is given at the beginning of the code:

Static final class<?>[] Mconstructorsignature = new class[] {context.class, attributeset.class};

Find a description of the GetConstructor function on Oracle's documentation:

Returns a constructor object that reflects the specified public constructor to the class represented by this class object. The Parametertypes parameter is a array of Class objects that identify the constructor ' s formal parameter types, in Decl Ared order. If This class object represents an inner class declared in a non-static context, the formal parameter the types include the EX Plicit enclosing instance as the parameter.

The constructor to reflect are the public constructor of the class represented by this class object whose formal Types match those specified by Parametertypes.

So here's the answer to the two questions: (1) The constructor returned by GetConstructor is the one whose arguments exactly match the parameters of the GetConstructor, and if not, throw the nosuchmethodexception exception. So we know that you have to have a constructor that exactly matches the mconstructorsignature, and you need the context and attributeset two parameters, (2) If the class represents a non-static inner class, Then an external class instance should be passed as the first argument. The incoming mconstructorsignature in our code does not contain an external class instance, so we have to declare our custom inner Class View as static to make no error. Some of the students explained that the non-static inner class needs to be referenced by an external class instance, but I think it will automatically construct an external class instance when the system is loaded. So here's the answer to the negative.

6. Get the class information Clazz, the code came to the "5", here to notice the Mconstructorargs, in the post code to give its definition, for object[2], and, through the source found, Mconstructorargs[0] is assigned to the context that was passed in when the Layoutinflater was created. So we know that in "5" here, the parameters passed to Newinstance are context and attributeset. Then there is also a statement in the ORACL documentation about Newinstance: If The constructor ' s declaring class is a inner class in a non-static context, the Firs T argument to the "constructor needs" enclosing instance; We also do not pass in the external class instance, which means that the static inner class also complains. This also verifies that the custom internal Class View must be declared as a static class problem.

Thus far, we have already assumed that using "view", again emphasizing lowercase ' V ', as an element name, introduces the three-point condition that must be met in order to apply the custom inner Class View in the XML, that is, to use "$" in the class attribute to connect the external class and the inner class. There must be a constructor that accepts the context and AttributeSet parameters, which must be declared static for these three requirements.

Instead of using a custom inner Class View in the form of a "view" label, when writing XML we find that we can only use the form of <com.willhua.myclass.myview/>, and not use the < Com.willhua.myclass$myview the/> form so that Androidstudio will report the "Tag start is not close" error. Obviously, if we use the form of <com.willhua.myclass.myview/>, then the LoadClass ("Com.willhua.MyClass.MyView") will be called at "Key 4". This is not consistent with the full naming rules for internal classes in Java, and will be an error. Some people would be careless to write in the form of Capital V, that is, <view class= "Com.willhua.myclass$myview" .../> form, which will be in the running times "wrong type" wrong, Because this essentially defines a android.view.View, you think of it as a defined Com.willhua.myclass$myview in your code.

At the identified "2", the calling process is Oncreateview (parent, name, Attrs)-->oncreateview (name, attrs)-->createview (name, " Android.view. ", Attrs), as mentioned earlier, the layoutinflater we really use is phonelayoutinflater, and in Phonelayoutinflater on this oncreateview ( The name, attrs) function is overridden, in Phonelayoutinflater's Oncreateview function, "6", which attempts to use three prefixes, respectively, by preceding name: "Android.widget.", " Android.webkit. "," Android.app. " To invoke CreateView, and if none is found, invoke the Oncreateview of the parent class to attempt to add "Android.view." Prefix to load the view. So, that's why we can use <button/> for example, <view/> Because these are commonly included in the four-pack names.

Summarize

At this point, we have found the reasons for the four requirements mentioned in the opening paragraph. In fact, as the whole process goes down, we find that there are three ways to get the defined elements to be loaded correctly into the relevant classes:

1. Use only the simple class name, that is, the form of <viewname/>. For classes in the four packages "Android.widget", "Android.webkit", "Android.app", and "Android.view", you can use this form directly, because the corresponding package name is automatically added to the code to form the complete path , but not for the other classes, because ViewName plus the full class names of the four package names cannot find the class.

2. Use the form of the < "full class name"/>, such as <com.willhua.myview/> or <android.widget.button/>, for any of the non-internal Class View, This is all OK. But not for internal classes, because such class names do not conform to the Java definition rules for the full class name of the inner class. If the form of <com.willhua.myclass$myview/> can be compiled, it must be able to correctly inflate the myview, but when the writing will be prompted wrong.

3. Use the form of <view class= "full class name"/>. This is the most common, all classes can do so, but only if the "full class name" is written to, such as the previous mentioned internal class using the "$" symbolic connection.

In order to understand the loading of the internal class view of the problem, the results of the entire loading process have a certain understanding, feel able to say more than a few why, or harvest quite large.
The above is the entire content of this article, I hope to help you learn, but also hope that we support the cloud habitat community.

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.