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(" 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 { // Dont 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 its 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 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的构造函数中进行加载的。
作者:kissazi2
出处:http://www.cnblogs.com/kissazi2/
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
转载:http://www.cnblogs.com/kissazi2/p/4310055.html
Android自定义控件(六)——文字波浪加载效果 前面介绍了贝济埃曲线,实现了波浪动画,也介绍了颜色叠加相关模式,比如其中的SRC_OUT模式实现了刮刮乐,今天将反过来以目标图像模式来是实现文字波浪加载动画效果。
相关文章
- Android telephony_android获取真实时间
- android开机动画多长时间_Android开机动画原理分析
- Ubuntu14.04安装Android SDK
- android 获取收到短信验证码,Android自动获取短信验证码
- android 验证码短信验证码,Android短信验证码倒计时验证的2种常用方式
- android 定时器实例,Android定时器和Handler用法实例分析
- android之实现打开相册、拍照录像、播放视频、保存图片到系统相册指定位置、图片压缩[通俗易懂]
- Android preference_android studio preview
- Android触摸事件_android设置按钮点击事件
- Android Studio debug使用release的签名
- Android so文件浅析「建议收藏」
- android跳转到相册需要权限,Android打开相册获取图片路径[通俗易懂]
- 自定义FlowLayout,android flowLayout实现
- 【技术分享】Android渗透测试
- 【Android 逆向】函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
- 【ijkplayer】编译 Android 版本的 ijkplayer ② ( 切换到 k0.8.8 分支 | 执行 init-android.sh 脚本进行初始化操作 )
- 【Android UI】Canvas 画布 ③ ( Canvas 图层栈 | Canvas#saveLayer() 新建图层 | Canvas 状态栈保存信息标志位 )
- 【Android UI】贝塞尔曲线 ⑤ ( 德卡斯特里奥算法 | 贝塞尔曲线递推公式 )
- [android] 线性布局和布局的组合详解手机开发
- Windows10真的要兼容Android App了 微软到底想玩什么?
- 开源新闻综述:谷歌开源 Android 语音转录和手势追踪、Twitter 的遥测工具
- Android实现PHP连接MySQL进行数据交互(android通过php连接mysql)
- Android系统自带样式(android:theme)
- Android通过LIstView显示文件列表的两种方法介绍
- Android中判断手机是否联网实例