还有两个问题没有解决。
(1) prov_city_list.json 下载后,如果变为 com.baidu.bus.f.b 的对象的?
在 3.加载城市列表 的第 14 步中,hObject 的成员 c 被赋了一个值,类型就是 com.baidu.bus.f.a,向上找这个对象是如何生成的:
invoke-static {v0, p1}, Lcom/baidu/bus/net/a/b;->a(Ljava/lang/String;Lcom/baidu/a/a/m;)Lcom/baidu/bus/f/a; move-result-object v0
看来是 com.baidu.bus.net.a.b 类的一个静态方法,传入了两个参数,第一个是解码(UTF-8)后的字符串文本,第二个是该函数(com.baidu.bus.net.a.c类的a() 方法)传进来的 com.baidu.a.a.m 的对象。在 com.baidu.bus.net.a.b 类的 a() 方法中,有这个代码:
iget-object v1, p1, Lcom/baidu/bus/base/f;->b:Ljava/lang/String; invoke-static {v1}, Lcom/baidu/bus/net/a/g;->a(Ljava/lang/String;)Lcom/baidu/bus/f/a;
调用了 com.baidu.bus.net.a.g 的 a() 方法,并传入了 com.baidu.bus.base.f 类的对象,实际就是第二个参数,com.baidu.a.a.m 是派生类。通过3.2的第(9)步,我们知道,这个实例的b成员的值是字符串“city_list”,在com.baidu.bus.net.a.g 类的 a() 方法中,有这样的代码:
:cond_0 const-string v1, "city_list" invoke-virtual {p0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z move-result v1 if-eqz v1, :cond_1 new-instance v0, Lcom/baidu/bus/f/b; invoke-direct {v0}, Lcom/baidu/bus/f/b;->()V
在这里创建了com.baidu.bus.f.b 的对象。在 com.baidu.bus.net.a.b 类中,取得这个 com.baidu.bus.f.b对象后,调用了它的 a() 方法:
move-result-object v1 invoke-virtual {v1, v0}, Lcom/baidu/bus/f/a;->a(Lcom/baidu/bus/base/e;)Lcom/baidu/bus/f/a;
通过查看 com.baidu.bus.f.b 类的 a() 方法,只不过是将JSONObject 中的 allList 和 hotCityList 进行解析,分别放到它的成员b和a(都是ArrayList)中。
至于JSON的解析,可以看到
new-instance v0, Lcom/baidu/bus/base/e; invoke-direct {v0, p0}, Lcom/baidu/bus/base/e;->(Ljava/lang/String;)V
这里利用了 com.baidu.bus.base.e 这个类,查看可知,它是从 JSONObject 派生的,实际的解析也是库函数。
(2) 每个省份包含的城市的详细信息,是如何取得的?
3.加载城市列表 介绍了获取 prov_city_list.json 的过程,下载的动作主要是在 com.baidu.bus.activity.ch 类中的进行的,已经知道这个类是从 AsyncTask 派生的,这个类有一个 onPostExecute() 方法,是在 doInBackground() 方法执行完成后,交给 UI 线程执行的动作。查看 ch 类重载了这个方法,除了将弹出的提示框的背景图片改为 loading_desc_checkofflinedatastatus(id 为 0x7f0200c9):
(原图是透明背景,白色字体,在这里为了显示方便,改为黑色背景)
更重要的,是调用了 OfflineDataManageActivity 里面的方法 j
iget-object v0, p0, Lcom/baidu/bus/activity/ch;->a:Lcom/baidu/bus/activity/OfflineDataManageActivity; invoke-static {v0}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->j(Lcom/baidu/bus/activity/OfflineDataManageActivity;)V
查看 OfflineDataManageActivity 里面的j方法,有两个循环,分别是对OfflineDataManageActivity 里的 M 和 N 两个成员(都是List类型)里面的每个元素,逐一调用 b() 方法:
check-cast v0, Lcom/baidu/bus/b/a; invoke-direct {p0, v0}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->b(Lcom/baidu/bus/b/a;)V
查看b() 方法,对传入的参数调用了另外一个成员 cm 的 execute() 方法,查看 cm 类,从 AsyncTask 派生,它的 doInBackground() 方法里调用它的 a() 方法,里面有如下代码:
iget v3, v0, Lcom/baidu/bus/b/a;->a:I invoke-static {v3}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; move-result-object v3 invoke-direct {v2, v3}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v2 invoke-virtual {v1, v2}, Lcom/baidu/bus/a/b;->a(Ljava/lang/String;)Lcom/baidu/bus/net/bean/update/DataUpdateInfo; move-result-object v1
根据上下文得知,v0 是传入的参数,v1 是 com.baidu.bus.a.b 的实例,它的 a() 方法是根据传入的参数构造了一个URL:
http://bs.baidu.com/offlinebusdata/{engineVersion}/{cityId}/meta.txt
上面的 engineVersion 为3(这个值是通过com.baidu.bus.d.b.a.a().d() 取得的,真正的值是在 JNI 即 libbusoffline.so 中,分析的过程在后面),cityId 就是从 prov_city_list.json 中解析出来的每个城市的id项。以北京为例,cityId 为131,那么构造的URL为:
http://bs.baidu.com/offlinebusdata/3/131/meta.txt
它返回一个JSON对象:
{"description":"", "downloadPath":"http://bs.baidu.com/offlinebusdata/3/131/131_14082210.dat", "MD5":"FAF26F270C83F1628DFAD85E16E2326E", "versionCode":"14082210", "engineVersion":3, "size":10560011}
这个就一目了然了。通过分析,我们知道,OfflineDataManageActivity 类里面的成员 M 是“已下载城市”,N 是“热门城市”。
从上面的分析可知,百度公交离线数据的下载方式是这样的:
1. 从 http://bs.baidu.com/offlinebusdata/prov_city_list.json 获取所有城市信息;
2. 单个城市的信息通过 http://bs.baidu.com/offlinebusdata/3/{cityId}/meta.txt 获取;
3. 该城市的离线数据从第2步中取得的 downloadPath 项获取。
在实际的操作中,可以不必分析这些smali文件,对于这个简单任务,使用 Wireshark 或者 tcpdump 之类的工具查看一下网络请求,就可以轻松得出结论。
但是,这样下载后的文件是乱码一样的东西,下面逐步解密该文件的格式。