热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android加载View中Background详解

本文讲解的是Android什么时候进行View中Background的加载,十分的详尽,十分全面细致,附上所有代码,这里推荐给大家,希望大家能够喜欢。

对大多数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(" 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<&#63; extends View> cOnstructor= sConstructorMap.get(name);
        Class<&#63; 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 &#63; (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&#63;
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null &#63; (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 &#63; (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 &#63; (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 &#63; "" : 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           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 &#63; value.data :
                (((long) value.assetCOOKIE) <<32) | value.data;
        Drawable dr = getCachedDrawable(isColorDrawable &#63; mColorDrawableCache : mDrawableCache, key);
        if (dr != null) {
            return dr;
        }
        Drawable.ConstantState cs = isColorDrawable
                &#63; sPreloadedColorDrawables.get(key)
                : (sPreloadedDensity == mConfiguration.densityDpi
                        &#63; 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(cs));
                        } else {
                            mDrawableCache.put(key, new WeakReference(cs));
                        }
                    }
                }
            }
        }
        return dr;
    }

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

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

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


推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • Echarts图表重复加载、axis重复多次请求问题解决记录
    文章目录1.需求描述2.问题描述正常状态:问题状态:3.解决方法1.需求描述使用Echats实现了一个中国地图:通过选择查询周期&#x ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
author-avatar
宋宝松示_989
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有