首页 > 娱乐前沿 > 热点
Android动态加载照片实例解析
佚名 2015-11-03 08:55:09

前不久跑去折腾高德 SDK 中的 HUD 功能,相信用过该功能的用户都知道 HUD 界面上的导航转向图标是动态变化的。从高德官方导航 API 文档中 AMapNaviGuide 类的描述可知,导航转向图标有23种类型。

诶,等等,23 种?那图标应该是放在 assets 文件夹吧?总不可能是在服务器上预告吧?

看下导航 API 的 jar 包结构。

AMap_ Navi_v1.3.0_20150828.jar
  |- assets
    |- autonavi_Resource1_1_0.png
    |- custtexture*.png (7 张)
  |- com
    |- amap.api.navi
    |- autonavi
  |- META-INF

纳尼?assets 上的图片总共也只有 8 张,而且图片的内容跟 HUD 毫无关系,莫非真的是从服务器预告照片?

用 Android Studio 打开 jar 包中的 AMapHudView.class 来看下 AMapHudView 的逻辑(AS 1.2 就引入了反编译功能)。

...
import com.autonavi.tbt.g;
...
public class AMapHudView extends FrameLayout implements OnClickListener, OnTouchListener, e {
	static final int[] hud_imgActions = new int[]{2130837532, 2130837532, 2130837532, 2130837533, 2130837534, 2130837535, 2130837536, 2130837537, 2130837538, 2130837539, 2130837522, 2130837523, 2130837524, 2130837525, 2130837526, 2130837527, 2130837528, 2130837529, 2130837530, 2130837531};
	...
	private ImageView roadsignimg;// 方向图标对应的 View
	...
	private int resId;// 方向图标的 id,对应 hud_imgActions 的 index,根据高德的文档,该变量值为 0-23
	...
	private void updateHudWidgetContent() {
        ...
        if(this.roadsignimg != null && this.resId != 0 && this.resId != 1) {
            Drawable var1 = g.a().getDrawable(hud_imgActions[this.resId]);// g.a() 返回的是 Resource 对象
            this.roadsignimg.setBackgroundDrawable(var1);
            ...
        }
    }
}

先看hud_imgActions,里面的值是不是很熟悉?转成16进制均为 0x7F02 开头(0x7F 是应用照片,而 0×02 则是 drawable 照片)。再看updateHudWidgetContent()方法,逻辑比较简单,通过resId获取hud_imgActions对应的 drawable id,再通过该 id 获取到对应的 Drawable 对象并将其设置到 ImageView 中。

看到这,可以肯定高德 SDK 最终是通过本地照片的索引获取到 Drawable。

然而我们的 apk 中并没有相应的照片,为什么能够正常获取到对应的 Drawable?我们看回上面的第12行代码:

Drawable var1 = g.a().getDrawable(hud_imgActions[this.resId]);// g.a() 返回的是 Resource 对象

我们将注意力集中到g.a()中,找到com.autonavi.tbt.g#a()

public static Resources a() {
    if (b == null) {
        b = e.getResources();
    }
    return b;
}

其中变量e为上层传递进来的 Activity,而我们前面说过,我们的 apk 中并没有相应的照片,所以将注意力放到变量b在其他地方的赋值上。

public static boolean a(Context context) {
    ...
    a = b(context.getFilesDir() + "/autonavi_Resource1_1_0.jar");
    b = a(context, a);// 变量 a 为 AssetManager
    return true;
}
private static AssetManager b(String str) {
    try {
        Class cls = Class.forName("android.content.res.AssetManager");
        AssetManager assetManager = (AssetManager) cls.getConstructor().newInstance();
        try {
            cls.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, str);
        } catch (Throwable th) {
        }
        return assetManager;
    } catch (Throwable th2) {
        return null;
    }
}
private static Resources a(Context context, AssetManager assetManager) {
    DisplayMetrics displayMetrics = new DisplayMetrics();
    displayMetrics.setToDefaults();
    return new Resources(assetManager, displayMetrics, context.getResources().getConfiguration());
}

可以看到,高德 SDK 中先通过反射实例化 AssetManager,并且调用 `addAssetPath(context.getFilesDir() + “/autonavi_Resource1_1_0.jar”),接着实例化 Resources 对象。所以事实上是通过这个新的 Resource 来获取到对应照片的 Drawable 对象。

但是我们的 apk 对应的 files 目录中并不存在 autonavi_Resource1_1_0.jar,这个文件又是怎么来的?

private static String k = "autonavi_Resource1_1_0.png";
...
private static boolean b(Context var0) {
	String filePath = var0.getFilesDir().getAbsolutePath() + "/autonavi_Resource1_1_0.jar";
	...
	InputStream var1 = var0.getResources().getAssets().open(k);
	File var3 = new File(filePath);
	long var21 = var3.length();
	int var6 = var1.available();
	if(!var3.exists() || var21 != (long)var6) {
	    ...
	    File var22 = new File(filePath);
	    FileOutputStream var2 = new FileOutputStream(var22);
	    byte[] var8 = new byte[1024];
	    int var9;
	    while((var9 = var1.read(var8)) > 0) {
	        var2.write(var8, 0, var9);
	    }
	}
	...
}

还是 com.autonavi.tbt.g 这个类,可以看到,高德是将 jar 包内 assets 目录中的 autonavi_Resource1_1_0.png 复制到当前 apk 对应的 files 目录中,并将新的文件命名为 autonavi_Resource1_1_0.jar。

再回到加载照片的问题上,为什么加载 autonavi_Resource1_1_0.jar 能索引照片?

因为该文件其实是 apk(高德将后缀名改成了 jar)。AssetManager 加载该 apk 后,Resource 就能通过该 AssetManager 获取到里面的相应照片。

AssetManager 的相关知识请参考老罗的《Android应用程序照片管理器(Asset Manager)的创建过程分析》

至此,我们就可以清楚知道高德 SDK 是如何实现动态加载照片的:

总结

将上述内容再简略,动态加载照片所必需的几个核心步骤:

这里需要注意的是,目标 apk(目录)需要放在context.getFilesDir()中,不然会加载失败(addAssetPath 返回 0)。另外,目标 apk 可以不签名,因为 addAssetPath 过程并没有进行签名校验。

获取照片 id

实际情况中,如果我们需要获取相应的照片,就必须先获得照片对应的 id,而外部 apk 的 R.java 并不属于主 apk,这就导致了获取照片的困难。

目前存在的解决方案有:

最后两种方案各有各的优缺点,至于怎么选择,还得结合自身的场景。

应用场景

动态加载照片技术目前的一些应用场景主要有:

后续

动态加载照片技术相关文章有很多,但就我目前所看到的文章只涉及如何获取 drawable、string 等照片,并没有发现关于动态加载照片 apk 中的布局文件(我姿势不对?_(:зゝ∠)_)。后续会分享如何动态加载照片 apk 中的布局文件。

上一篇  下一篇

I 相关 / Other

无与伦比 【蒙娜丽莎】包袋推荐


如果说有哪个品牌兼具轻奢、休闲、艺术、个性等各种风格,答案只有一个,唯有蒙娜丽莎。蒙娜丽莎皮

秋季套头衫搭配 套头衫单品推荐


现在无论给你介绍什么款式应该你都能接受吧,可能对于现在的社会就是少了点创新,所以很多款式都需

Android架构演化之路

大家好! 过了一好阵子了(在此期间我收到了大量的读者反馈) 我决定是时候回到手机程序架构这个话题上了(这里

使用RelProxy提高Java开发效率

RelProxy 旨在通过下列两种方式提高开发效率:可以在生产环境下修改用户代码,而不需要重新加载整个应用。提

Android网络框架OKHttp学习

OKHTTPokHttp: OKHttp是Android版Http客户端。非常高效,支持SPDY、连接池、GZIP和 HTTP 缓存。默认情况下,

I 热点 / Hot