Android ImageView 图片加载流程

Android ImageView 源码解读,通过分析ImageView设置图片的流程,查看ImageView是如何加载图片的,可以帮助我们更好的理解图片加载,以及性能优化。

setImageResource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* This does Bitmap reading and decoding on the UI
* thread, which can cause a latency hiccup. If that's a concern,
* consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
* {@link #setImageBitmap(android.graphics.Bitmap)} and
* {@link android.graphics.BitmapFactory} instead.</p>
*/
public void setImageResource(@DrawableRes int resId) {
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(null); // 清空原来的 Drawable
mResource = resId; // 保存 resourceId
mUri = null;
resolveUri(); // 重新加载 Drawable
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}

google的注释已经说了,通过设置 resId,图片的解码过程是在UI线程进行的,所以,如果图片比较大的话,考虑到性能的问题,可以使用setImageDrawable或者setImageBitmap代替。

resolveUri

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void resolveUri() {
// ...
Drawable d = null;
if (mResource != 0) {
try {
d = mContext.getDrawable(mResource); // 使用 Context 加载 Drawable
} catch (Exception e) {
Log.w(LOG_TAG, "Unable to find resource: " + mResource, e);
mUri = null;
}
} else if (mUri != null) {
d = getDrawableFromUri(mUri);
if (d == null) {
Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
mUri = null;
}
} else {
return;
}
updateDrawable(d); // 更新 Drawable
}

getDrawable

mContext.getDrawable 通过 Resources.getDrawable,最终会调用到ResourcesImpl.loadDrawable,真正的去加载图片,在加载图片的时候,会先判断是否是系统资源id,以及颜色值图片,如果不是,则加载图片文件,先从缓存中查看有没有该资源id对应的图片,如果没有,才真正的去加载图片文件,加载完成后系统会把该图片文件缓存起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Resources.java
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
return impl.loadDrawable(this, value, id, theme, true);
} finally {
releaseTempTypedValue(value);
}
}
// ResourcesImpl.java
@Nullable
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
boolean useCache) throws NotFoundException {
try {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources # 系统资源文件
if ((id >>> 24) == 0x1) {
final String name = getResourceName(id);
if (name != null) {
Log.d("PreloadDrawable", name);
}
}
}
final boolean isColorDrawable;
final DrawableCache caches; // 缓存集合
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) { // 使用 颜色值的Drawable
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else { // 图片Drawable
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
// 如果有缓存的话,从缓存中拿出 Drawable直接返回
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
// Next, check preloaded drawables. Preloaded drawables may contain
// unresolved theme attributes.
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else { // 根据资源id获取图片
dr = loadDrawableForCookie(wrapper, value, id, null);
}
// applyTheme
final boolean canApplyTheme = dr != null && dr.canApplyTheme();
if (canApplyTheme && theme != null) {
dr = dr.mutate();
dr.applyTheme(theme);
dr.clearMutated();
}
if (dr != null && useCache) {
dr.setChangingConfigurations(value.changingConfigurations);
// 缓存加载出来的图片
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
return dr;
} catch (Exception e) {
}
}

loadDrawableForCookie

加载图片文件,该流程会根据是否是xml文件,进行不同的加载操作,例如我们使用xml定义的drawable,加载图片文件的时候,判断是否是 .9 文件,分别返回 NinePatchDrawableBitmapDrawable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) {
// ...
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(wrapper, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
} catch (Exception e) {
}
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return dr;
}
public static Drawable createFromResourceStream(Resources res, TypedValue value,
InputStream is, String srcName, BitmapFactory.Options opts) {
// ..
Rect pad = new Rect();
if (opts == null) opts = new BitmapFactory.Options();
opts.inScreenDensity = Drawable.resolveDensity(res, 0);
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
if (bm != null) {
byte[] np = bm.getNinePatchChunk();
if (np == null || !NinePatch.isNinePatchChunk(np)) {
np = null;
pad = null;
}
final Rect opticalInsets = new Rect();
bm.getOpticalInsets(opticalInsets);
return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
}
return null;
}
private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
Rect pad, Rect layoutBounds, String srcName) {
if (np != null) {
return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
}
return new BitmapDrawable(res, bm);
}

setImageBitmap

1
2
3
4
5
6
7
8
9
10
@android.view.RemotableViewMethod
public void setImageBitmap(Bitmap bm) {
mDrawable = null;
if (mRecycleableBitmapDrawable == null) {
mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
} else {
mRecycleableBitmapDrawable.setBitmap(bm);
}
setImageDrawable(mRecycleableBitmapDrawable);
}

updateDrawable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void updateDrawable(Drawable d) {
if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
mRecycleableBitmapDrawable.setBitmap(null);
}
boolean sameDrawable = false;
if (mDrawable != null) {
sameDrawable = mDrawable == d;
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
mDrawable.setVisible(false, false);
}
}
mDrawable = d;
if (d != null) {
d.setCallback(this); // 将 drawable 的 callback 对象设置为 ImageView本身
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
final boolean visible = sCompatDrawableVisibilityDispatch
? getVisibility() == VISIBLE
: isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
d.setVisible(visible, true);
}
d.setLevel(mLevel);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyImageTint();
applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}

需要注意的是 d.setCallback ,该方法将Drawable 的 callback 设置为 ImageView本身,所以,使用 Drawable的时候需要注意,不能在 Activity或Fragment定义静态的drawable,一旦一个静态的drawable设置给了一个ImageView,则drawalbe一直持有ImageView对象,ImageView对象持有Activity或Fragment所属的Activity对象,就造成该Activity不能正确被回收。