上一篇文章讲到SVG-Android框架帮我们生成了svg.xml的渲染类来帮助我们画图,从而达到矢量图不失真的效果。如果没有看过上篇的小伙伴请先看上一篇博客SVG-Android(gradle插件生成器)源码详解,在生成好这些渲染类之后,只需要在在你的项目的Application中加入SVGLoader.load(this)加入这句代码,然后在控件上(在5.0以下系统)像使用普通的drable一样的使用svg.xml格式的文件了。
SVGLoader.load(this)帮我们做了什么,怎么有这么神奇的效果
sPreloadedDrawables = SVGHelper.hackPreloadDrawables(context.getResources());
if (sPreloadedDrawables == null) {
return;
}
add(context, R.drawable.ic_android_red, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red(context)));
......
省略号下的代码都是add,这里不再列出,这个方法做的工作就是将所有生成的渲染类加入集合,通过图片id标识。这里的sPreloadedDrawables 为LongSparseArray<Drawable.ConstantState>,那么它是怎么得到呢?SVGHelper.hackPreloadDrawables(context.getResources())进入这个方法一探究竟
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return hackPreloadDrawablesV15(res);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR2) {
return hackPreloadDrawablesV18(res);
} else {
return hackPreloadDrawablesV19(res);
}
很明显这里进行了版本判断,如果当前系统版本低于4.3用hackPreloadDrawablesV15,是4.3的话用hackPreloadDrawablesV18处理,否则用hackPreloadDrawablesV19处理,好,先从4.3以下版本看起
private static LongSparseArray<Drawable.ConstantState> hackPreloadDrawablesV15(Resources res) {
try {
Field field = Resources.class.getDeclaredField("sPreloadedDrawables");
field.setAccessible(true);
return (LongSparseArray<Drawable.ConstantState>) field.get(res);
} catch (NoSuchFieldException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
}
return null;
}
这个方法很明显是利用反射获取到Framwork层Resources 对象的sPreloadedDrawables属性,既然上方采用了版本判断,那么说明这几个级别的android的系统的Resources源码略有不同,那么这个属性到底是做什么的呢,ok现在以4.0源码为例看看Resource究竟干了什么!打开Resources源码,果然有这么一个属性
private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables
= new LongSparseArray<Drawable.ConstantState>();
这个属性只有在loadDrawable方法里面有使用,如下:
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);
}
}
final long key = (((long) value.assetCookie) << 32) | value.data;
boolean isColorDrawable = false;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
}
//获得缓存中是否有drawable
Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
if (dr != null) {
return dr;
}
//解析drable是哪种类型的
//最重要的部分,记得application里面向集合添加的什么吗,其实实现了替换的作用
Drawable.ConstantState cs = isColorDrawable ?
sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
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 (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;
}
最重要的部分是第25行,还记得我们在Aplication添加的什么吗,再贴一下
add(context, R.drawable.ic_android_red, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red(context)));
我们朝sPreloadedDrawables加入了若干我们自定义的VGDrawableConstantState,所以每次想获取svg.xml时都会调用我们自己的VGDrawableConstantState去创建Drable,从而实现替代作用,也就是现在经常提的黑科技,原理和插件化原理有点类似。这里需要注意一点SVGDrawableConstantStateextendsConstantState SVGDrawableConstantState 是基于系统的类。接下来看一下其他版本的获取,系统4.3版本的如下
private static LongSparseArray<Drawable.ConstantState> hackPreloadDrawablesV18(Resources res) {
try {
Field field = Resources.class.getDeclaredField("sPreloadedDrawables");
field.setAccessible(true);
return ((LongSparseArray<Drawable.ConstantState>[]) field.get(res))[0];
} catch (NoSuchFieldException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
}
return null;
}
系统4.4版本以上的获取
private static LongSparseArray<Drawable.ConstantState> hackPreloadDrawablesV19(Resources res) {
try {
Method method = Resources.class.getDeclaredMethod("getPreloadedDrawables");
method.setAccessible(true);
return (LongSparseArray<Drawable.ConstantState>) method.invoke(res);
} catch (NoSuchMethodException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (InvocationTargetException e) {
// ignore
}
return null;
}
这里注意了,如果我们想自己hook源码的话,必须得看多个版本的源码,因为Google每次升级都有可能改变源码的一些细节,导致hook源码在不同系统的不准确性,既然这部分被替换掉了,那么loadDrawable方法什么时候被调用呢?此时怎么想也应该在控件设置背景,或者src时传入drable的id的时候调用的,ok,以ImageView控件的setImageResource为例
public void setImageResource(@DrawableRes int resId) {
// The resource configuration may have changed, so we should always
// try to load the resource even if the resId hasn't changed.
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(null);
mResource = resId;
mUri = null;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
这里的Drawable是通过resolveUri()方法来获取的
private void resolveUri() {
if (mDrawable != null) {
return;
}
if (getResources() == null) {
return;
}
Drawable d = null;
if (mResource != 0) {
try {
d = mContext.getDrawable(mResource);
} catch (Exception e) {
Log.w(LOG_TAG, "Unable to find resource: " + mResource, e);
// Don't try again.
mUri = null;
}
} else if (mUri != null) {
d = getDrawableFromUri(mUri);
if (d == null) {
Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
// Don't try again.
mUri = null;
}
} else {
return;
}
最终Drawable是通过context的getDrawable方法将id转化为|Drawable的,而context的真正实现类为ContextImpl,为什么是它,如果你对这个不明白的话,请看一看关于app进程启动的入口类ActivityThread,不过这里最后的获取还是在父类Context中实现的,如下:
public final Drawable getDrawable(@DrawableRes int id) {
return getResources().getDrawable(id);
}
最后又交给Resources的getDrawable方法
public Drawable getDrawable(int id) throws NotFoundException {
synchronized (mTmpValue) {
TypedValue value = mTmpValue;
getValue(id, value, true);
return loadDrawable(value, id);
}
}
看到了什么,最后还是调用了loadDrawable方法来加载drable,而调用这个方法的时候集合中已经存在数据,所以就会加载我们已经加载到集合的SVGDrawableConstantState类,然后通过
public Drawable newDrawable() {
return new SVGDrawable(this);
}
获取SVGDrawable类,ok,最后的Drable类是框架中的一个类SVGDrawable,这又设计到自定义Drawable的知识了,这一块小伙伴们可以查看文档或者百度一下,这里不再阐述,
自定义Drawable最重要的一个方法就是Drawable,最后是调用的draw方法将需要的图形画入,好看一下
public void draw(@NonNull Canvas canvas) {
// We will offset the bounds for draw, so copyBounds() here instead
// of getBounds().
copyBounds(mTmpBounds);
if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
// Nothing to draw
return;
}
// Color filters always override tint filters.
final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
canvas.getMatrix(mTmpMatrix);
mTmpMatrix.getValues(mTmpFloats);
float canvasScaleX = Math.abs(mTmpFloats[Matrix.MSCALE_X]);
float canvasScaleY = Math.abs(mTmpFloats[Matrix.MSCALE_Y]);
float canvasSkewX = Math.abs(mTmpFloats[Matrix.MSKEW_X]);
float canvasSkewY = Math.abs(mTmpFloats[Matrix.MSKEW_Y]);
// When there is any rotation / skew, then the scale value is not valid.
if (canvasSkewX != 0 || canvasSkewY != 0) {
canvasScaleX = 1.0f;
canvasScaleY = 1.0f;
}
int scaledWidth = (int) (mTmpBounds.width() * canvasScaleX);
int scaledHeight = (int) (mTmpBounds.height() * canvasScaleY);
if (scaledWidth <= 0 || scaledHeight <= 0) {
return;
}
final int saveCount = canvas.save();
canvas.translate(mTmpBounds.left, mTmpBounds.top);
// Handle RTL mirroring.
final boolean needMirroring = needMirroring();
if (needMirroring) {
canvas.translate(mTmpBounds.width(), 0);
canvas.scale(-1.0f, 1.0f);
}
// At this point, canvas has been translated to the right position.
// And we use this bound for the destination rect for the drawBitmap, so
// we offset to (0, 0);
mTmpBounds.offsetTo(0, 0);
// Use the renderer to draw.
mState.mRenderer.draw(canvas, scaledWidth, scaledHeight, colorFilter, mTmpBounds);
canvas.restoreToCount(saveCount);
}
直接定位位到321行mState.mRenderer.draw(canvas, scaledWidth, scaledHeight, colorFilter, mTmpBounds),最后的画图是在Gradle为每一个svg.xml生成的渲染类实现的,也就是把svg.xml文件中的path标签中的路径映射到渲染类的Path路径中,最后通过SVGDrawable间接将路径画出来,从而实现矢量图的呈现。从最上面的版本判断可以看出,作者并没有看4.0以下的源码是怎么加载Drawable的,所以说此框架只兼容到4.0,不过以现在的手机市场,版本系统所占的比例,也没有必用兼容到4.0以下的了。