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 { // 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的建構函式中進行載入的。
相關文章
- android view background 問題AndroidView
- Android 中 View 繪製流程分析AndroidView
- Android 中 View 炸裂特效的實現分析AndroidView特效
- 載入時間/效能
- android view 分析AndroidView
- Android程式中的時間Android
- Android 自定義View實戰系列 :時間軸AndroidView
- Android 中Activity,Window和View之間的關係AndroidView
- Android開機時間分析Android
- Android 自定義 View 實現橫行時間軸AndroidView
- Android自定義View:快遞時間軸實現AndroidView
- Android執行時ART載入OAT檔案的過程分析Android
- Android執行時ART載入類和方法的過程分析Android
- Laravel Eloquent中的 懶載入VS即時載入Laravel
- Android自定義View封裝的android頁面載入的幾種情況!AndroidView封裝
- Android中Button設定background過程的研究Android
- Activity、View、Window之間關係的分析View
- android-smart-image-view圖片載入簡單使用AndroidView
- web效能之資源載入時間分析【Resource Timing】【原創】Web
- android view layout原始碼分析AndroidView原始碼
- Controller和View物件的載入ControllerView物件
- View的載入原理和攔截方式View
- View繪製01-Android渲染系統中的ViewViewAndroid
- 【Android原始碼】View的繪製流程分析Android原始碼View
- Android中-Loader載入器Android
- Android 自定義view中的屬性,名稱空間,以及tools標籤AndroidView
- 深入解析 Android 中 View 的工作原理AndroidView
- Android fragment 標籤載入過程分析AndroidFragment
- Android 中如何計算 App 的啟動時間?AndroidAPP
- Android中如何計算App的啟動時間?AndroidAPP
- 一種新的頁面載入時間檢測方式
- android 在擷取指定View的時候坑AndroidView
- Android讓Fragment載入到Activity中AndroidFragment
- Android View 事件分發原始碼分析AndroidView事件原始碼
- android view draw原始碼過程分析AndroidView原始碼
- Android備忘錄《View動畫(補間動畫)》AndroidView動畫
- Android洩漏模式:View中的訂閱Android模式View
- Android view中的requestLayout和invalidate方法AndroidView