欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android加载View中Background详解

程序员文章站 2022-03-23 14:13:15
对大多数android的开发者来说,最经常的操作莫过于对界面进行布局,view中背景图片的加载是最经常做的。但是我们很少关注这个过程,这篇文章主要解析view中背景图片加载...

对大多数android的开发者来说,最经常的操作莫过于对界面进行布局,view中背景图片的加载是最经常做的。但是我们很少关注这个过程,这篇文章主要解析view中背景图片加载的流程。了解view中背景图片的加载(资源的加载)可以让我们对资源加载的过程进行一些优化,另外当需要进行整个应用的换肤时,也可以更得心应手。

view图片的加载,我们最常见的就是通过在xml文件当中进行drawable的设置,然后让android系统帮我们完成,或者手动写代码加载成bitmap,然后加载到view上。这篇文章主要分析android在什么时候以及怎么帮我们完成背景图片的加载的,那么我们就从activity.setcontentview还是layoutinflater.inflate(...)方法开始分析。

不管是从activity.setcontentview(...)还是layoutinflater.inflate(...)方法进行view的初始化,最终都会到达layoutinflater.inflate(xmlpullparser parser, viewgroup root, boolean attachtoroot)这个方法中。在这里我们主要关注view的背景图片加载,对于xml如何解析和加载就放过了。

复制代码 代码如下:

    public view inflate(xmlpullparser parser, viewgroup root, boolean attachtoroot) {
        synchronized (mconstructorargs) {
            final attributeset attrs = xml.asattributeset(parser);
            context lastcontext = (context)mconstructorargs[0];
            mconstructorargs[0] = mcontext;
            view result = root;
            try {
                // look for the root node.
                int type;
                while ((type = parser.next()) != xmlpullparser.start_tag &&
                        type != xmlpullparser.end_document) {
                    // empty
                }
                if (type != xmlpullparser.start_tag) {
                    throw new inflateexception(parser.getpositiondescription()
                            + ": no start tag found!");
                }
                final string name = parser.getname();
                if (debug) {
                    system.out.println("**************************");
                    system.out.println("creating root view: "
                            + name);
                    system.out.println("**************************");
                }
                if (tag_merge.equals(name)) {
                    if (root == null || !attachtoroot) {
                        throw new inflateexception("<merge /> can be used only with a valid "
                                + "viewgroup root and attachtoroot=true");
                    }
                    rinflate(parser, root, attrs, false);
                } else {
                    // temp is the root view that was found in the xml
                    view temp;
                    if (tag_1995.equals(name)) {
                        temp = new blinklayout(mcontext, attrs);
                    } else {
                        temp = createviewfromtag(root, name, attrs);
                    }
                    viewgroup.layoutparams params = null;
                    if (root != null) {
                        if (debug) {
                            system.out.println("creating params from root: " +
                                    root);
                        }
                        // create layout params that match root, if supplied
                        params = root.generatelayoutparams(attrs);
                        if (!attachtoroot) {
                            // set the layout params for temp if we are not
                            // attaching. (if we are, we use addview, below)
                            temp.setlayoutparams(params);
                        }
                    }
                    if (debug) {
                        system.out.println("-----> start inflating children");
                    }
                     // inflate all children under temp
                    rinflate(parser, temp, attrs, true);
                    if (debug) {
                        system.out.println("-----> done inflating children");
                    }
                    // we are supposed to attach all the views we found (int temp)
                    // to root. do that now.
                    if (root != null && attachtoroot) {
                        root.addview(temp, params);
                    }
                    // decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachtoroot) {
                        result = temp;
                    }
                }
            } catch (xmlpullparserexception e) {
                inflateexception ex = new inflateexception(e.getmessage());
                ex.initcause(e);
                throw ex;
            } catch (ioexception e) {
                inflateexception ex = new inflateexception(
                        parser.getpositiondescription()
                        + ": " + e.getmessage());
                ex.initcause(e);
                throw ex;
            } finally {
                // don't retain static reference on context.
                mconstructorargs[0] = lastcontext;
                mconstructorargs[1] = null;
            }
            return result;
        }
    }
上面这么长一串代码,其实思路很清晰,就是针对xml文件进行解析,然后根据xml解析出的每一个节点进行view的初始化,紧接着将view的layout参数设置到view上,然后将view添加到它的父控件上。
为了了解view是怎么被加载出来的,我们只需要了解
 temp = createviewfromtag(root, name, attrs);
跟进去看看。
    /*
     * default visibility so the bridgeinflater can override it.
     */
    view createviewfromtag(view parent, string name, attributeset attrs) {
        if (name.equals("view")) {
            name = attrs.getattributevalue(null, "class");
        }
        if (debug) system.out.println("******** creating view: " + name);
        try {
            view view;
            if (mfactory2 != null) view = mfactory2.oncreateview(parent, name, mcontext, attrs);
            else if (mfactory != null) view = mfactory.oncreateview(name, mcontext, attrs);
            else view = null;
            if (view == null && mprivatefactory != null) {
                view = mprivatefactory.oncreateview(parent, name, mcontext, attrs);
            }
            if (view == null) {
                if (-1 == name.indexof('.')) {
                    view = oncreateview(parent, name, attrs);
                } else {
                    view = createview(name, null, attrs);
                }
            }
            if (debug) system.out.println("created view is: " + view);
            return view;
        } catch (inflateexception e) {
            throw e;
        } catch (classnotfoundexception e) {
            inflateexception ie = new inflateexception(attrs.getpositiondescription()
                    + ": error inflating class " + name);
            ie.initcause(e);
            throw ie;
        } catch (exception e) {
            inflateexception ie = new inflateexception(attrs.getpositiondescription()
                    + ": error inflating class " + name);
            ie.initcause(e);
            throw ie;
        }
    }

上面代码的重点在于try...catch里的内容。try包起来的东西就是对view进行初始化,注意到上面代码中有几个factory,这些factory可以在view进行初始化,也就是说其实我们可以在这里干预view的初始化。从上面代码我们可以知道,如果我们自定义了一个factory,那么当前要初始化的view会优先被我们自定义的factory初始化,而不通过系统默认的factory初始化。那么如果我们要自定义factory,应该在哪里定义呢?容易想到,factory必须要赶在资源加载前自定义完成,所以我们应该在oncreate(...)的this.setcontentview(...)之前设置layoutinflater.factory。

  getlayoutinflater().setfactory(factory);
接下来我们看到上面函数里面的

复制代码 代码如下:

  if (-1 == name.indexof('.')) {
        view = oncreateview(parent, name, attrs);
    } else {
        view = createview(name, null, attrs);
    }

这段函数就是对view进行初始化,有两种情况,一种是系统自带的view,它在

 if (-1 == name.indexof('.'))
这里面进行初始化,因为如果是系统自带的view,传入的那么一般不带系统的前缀"android.view."。另一个分支初始化的是我们自定义的view。我们跟进oncreateview看看。

复制代码 代码如下:

  protected view oncreateview(string name, attributeset attrs)
            throws classnotfoundexception {
        return createview(name, "android.view.", attrs);
    }
    public final view createview(string name, string prefix, attributeset attrs)
            throws classnotfoundexception, inflateexception {
        constructor<? extends view> constructor = sconstructormap.get(name);
        class<? extends view> clazz = null;
        try {
            if (constructor == null) {
                // class not found in the cache, see if it's real, and try to add it
                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);
                sconstructormap.put(name, constructor);
            } else {
                // if we have a filter, apply it to cached constructor
                if (mfilter != null) {
                    // have we seen this name before?
                    boolean allowedstate = mfiltermap.get(name);
                    if (allowedstate == null) {
                        // new class -- remember whether it is allowed
                        clazz = mcontext.getclassloader().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;
            final view view = constructor.newinstance(args);
            if (view instanceof viewstub) {
                // always use ourselves when inflating viewstub later
                final viewstub viewstub = (viewstub) view;
                viewstub.setlayoutinflater(this);
            }
            return view;
        } catch (nosuchmethodexception e) {
            inflateexception ie = new inflateexception(attrs.getpositiondescription()
                    + ": error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initcause(e);
            throw ie;
        } catch (classcastexception e) {
            // if loaded class is not a view subclass
            inflateexception ie = new inflateexception(attrs.getpositiondescription()
                    + ": class is not a view "
                    + (prefix != null ? (prefix + name) : name));
            ie.initcause(e);
            throw ie;
        } catch (classnotfoundexception e) {
            // if loadclass fails, we should propagate the exception.
            throw e;
        } catch (exception e) {
            inflateexception ie = new inflateexception(attrs.getpositiondescription()
                    + ": error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getname()));
            ie.initcause(e);
            throw ie;
        }
    }

从oncreateview(...)中我们知道,其实createviewfromtag(...)中对view的初始化最终都是通过createview(...)这个函数进行初始化的,不同只在于系统控件需要通过oncreateview(...)加上前缀,以便类加载器(classloader)正确地通过类所在的包初始化这个类。createview(...)这个函数的思路很清晰,不看catch里面的内容,try里面开头的两个分支就是用来将所要用的类构造函数提取出来,android系统会对使用过的类构造函数进行缓存,因为像textview这些常用的控件可能会被使用很多次。接下来,就是通过类构造函数对view进行初始化了。我们注意到传入构造函数的mconstructorargs是一个包含两个元素的数组。

 final object[] mconstructorargs = new object[2];
那么我们就很清楚了,它就是调用系统控件中对应两个参数的构造函数。为了方便,我们就从最基础的view进行分析。

复制代码 代码如下:

  public view(context context, attributeset attrs) {
        this(context, attrs, 0);
    }
    public view(context context, attributeset attrs, int defstyle) {
     this(context);
     typedarray a = context.obtainstyledattributes(attrs, com.android.internal.r.styleable.view,
             defstyle, 0);
     drawable background = null;
     int leftpadding = -1;
     int toppadding = -1;
     int rightpadding = -1;
     int bottompadding = -1;
     int startpadding = undefined_padding;
     int endpadding = undefined_padding;
     int padding = -1;
     int viewflagvalues = 0;
     int viewflagmasks = 0;
     boolean setscrollcontainer = false;
     int x = 0;
     int y = 0;
     float tx = 0;
     float ty = 0;
     float rotation = 0;
     float rotationx = 0;
     float rotationy = 0;
     float sx = 1f;
     float sy = 1f;
     boolean transformset = false;
     int scrollbarstyle = scrollbars_inside_overlay;
     int overscrollmode = moverscrollmode;
     boolean initializescrollbars = false;
     boolean leftpaddingdefined = false;
     boolean rightpaddingdefined = false;
     boolean startpaddingdefined = false;
     boolean endpaddingdefined = false;
     final int targetsdkversion = context.getapplicationinfo().targetsdkversion;
     final int n = a.getindexcount();
      for (int i = 0; i < n; i++) {
          int attr = a.getindex(i);
          switch (attr) {
              case com.android.internal.r.styleable.view_background:
                  background = a.getdrawable(attr);
                  break;
              case com.android.internal.r.styleable.view_padding:
                  padding = a.getdimensionpixelsize(attr, -1);
                  muserpaddingleftinitial = padding;
                  muserpaddingrightinitial = padding;
                  leftpaddingdefined = true;
                  rightpaddingdefined = true;
                  break;
   //省略一大串无关的函数
 }

由于我们只关注view中的背景图是怎么加载的,注意这个函数其实就是遍历attributeset attrs这个东西,然后对view的各个属性进行初始化。我们直接进入

 background = a.getdrawable(attr);
这里看看(typedarray.getdrawable)。

复制代码 代码如下:

    public drawable getdrawable(int index) {
        final typedvalue value = mvalue;
        if (getvalueat(index*assetmanager.style_num_entries, value)) {
            if (false) {
                system.out.println("******************************************************************");
                system.out.println("got drawable resource: type="
                                   + value.type
                                   + " str=" + value.string
                                   + " int=0x" + integer.tohexstring(value.data)
                                   + " cookie=" + value.assetcookie);
                system.out.println("******************************************************************");
            }
            return mresources.loaddrawable(value, value.resourceid);
        }
        return null;
    }

我们发现它调用mresources.loaddrawable(...),进去看看。

复制代码 代码如下:

    /*package*/ drawable loaddrawable(typedvalue value, int id)
            throws notfoundexception {
        if (trace_for_preload) {
            // log only framework resources
            if ((id >>> 24) == 0x1) {
                final string name = getresourcename(id);
                if (name != null) android.util.log.d("preloaddrawable", name);
            }
        }
        boolean iscolordrawable = false;
        if (value.type >= typedvalue.type_first_color_int &&
                value.type <= typedvalue.type_last_color_int) {
            iscolordrawable = true;
        }
        final long key = iscolordrawable ? value.data :
                (((long) value.assetcookie) << 32) | value.data;
        drawable dr = getcacheddrawable(iscolordrawable ? mcolordrawablecache : mdrawablecache, key);
        if (dr != null) {
            return dr;
        }
        drawable.constantstate cs = iscolordrawable
                ? spreloadedcolordrawables.get(key)
                : (spreloadeddensity == mconfiguration.densitydpi
                        ? spreloadeddrawables.get(key) : null);
        if (cs != null) {
            dr = cs.newdrawable(this);
        } else {
            if (iscolordrawable) {
                dr = new colordrawable(value.data);
            }
            if (dr == null) {
                if (value.string == null) {
                    throw new notfoundexception(
                            "resource is not a drawable (color or path): " + value);
                }
                string file = value.string.tostring();
                if (trace_for_miss_preload) {
                    // log only framework resources
                    if ((id >>> 24) == 0x1) {
                        final string name = getresourcename(id);
                        if (name != null) android.util.log.d(tag, "loading framework drawable #"
                                + integer.tohexstring(id) + ": " + name
                                + " at " + file);
                    }
                }
                if (debug_load) log.v(tag, "loading drawable for cookie "
                        + value.assetcookie + ": " + file);
                if (file.endswith(".xml")) {
                    try {
                        xmlresourceparser rp = loadxmlresourceparser(
                                file, id, value.assetcookie, "drawable");
                        dr = drawable.createfromxml(this, rp);
                        rp.close();
                    } catch (exception e) {
                        notfoundexception rnf = new notfoundexception(
                            "file " + file + " from drawable resource id #0x"
                            + integer.tohexstring(id));
                        rnf.initcause(e);
                        throw rnf;
                    }
                } else {
                    try {
                        inputstream is = massets.opennonasset(
                                value.assetcookie, file, assetmanager.access_streaming);
        //                system.out.println("opened file " + file + ": " + is);
                        dr = drawable.createfromresourcestream(this, value, is,
                                file, null);
                        is.close();
        //                system.out.println("created stream: " + dr);
                    } catch (exception e) {
                        notfoundexception rnf = new notfoundexception(
                            "file " + file + " from drawable resource id #0x"
                            + integer.tohexstring(id));
                        rnf.initcause(e);
                        throw rnf;
                    }
                }
            }
        }
        if (dr != null) {
            dr.setchangingconfigurations(value.changingconfigurations);
            cs = dr.getconstantstate();
            if (cs != null) {
                if (mpreloading) {
                    if (verifypreloadconfig(value, "drawable")) {
                        if (iscolordrawable) {
                            spreloadedcolordrawables.put(key, cs);
                        } else {
                            spreloadeddrawables.put(key, cs);
                        }
                    }
                } else {
                    synchronized (mtmpvalue) {
                        //log.i(tag, "saving cached drawable @ #" +
                        //        integer.tohexstring(key.intvalue())
                        //        + " in " + this + ": " + cs);
                        if (iscolordrawable) {
                            mcolordrawablecache.put(key, new weakreference<drawable.constantstate>(cs));
                        } else {
                            mdrawablecache.put(key, new weakreference<drawable.constantstate>(cs));
                        }
                    }
                }
            }
        }
        return dr;
    }

就是这个函数了,所有view的背景的加载都在这里了。这个函数的逻辑就比较复杂了,大体说来就是根据背景的类型(纯颜色、定义在xml文件中的,或者是一张静态的背景),如果缓存里面有,就直接用缓存里的。

总结一下,经过上面的分析,我们知道了,android就是在activity.setcontentview(...)中为我们进行资源文件的加载,精确到具体的函数的话,资源文件的加载就是在每一个被初始化的view的构造函数中进行加载的。

以上就是本文的全部内容了,希望对大家能够有所帮助。