編輯:關於Android編程
對大多數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的構造函數中進行加載的。
以上就是本文的全部內容了,希望對大家能夠有所幫助。
著手開發一款應用的時候,設置或者菜單頁面是可能需要的,但是,那重復的布局會很令人苦惱。新手可能會一項項的重復繪制,有經驗的你或許會用到include,或者用到組合控件。除
這個錯誤翻譯的意思是:不能在沒有Looper.prepare的線程裡面創建handler。起初我很疑惑,我根本沒有用到工作線程,也沒有創建handler。報錯的代碼如下:
Android應用內社區SDK技術架構淺析應用內微社區是什麼 ? 圖1 圖2 雪球財經應用內社區 一、以友盟微社區為例簡單來說,友盟的微社區就是一款幫助
1.LinearLayout LinearLayout中有一個屬性是android:orientation,vertical是垂直排列,horizontal是水