[原]百度公交离线数据格式分析——3.加载城市列表

1. 在进入OfflineDataManageActivity时,找到 onCreate() 方法,在最后几行:

new-instance v0, Lcom/baidu/bus/d/i;
iget-object v1, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->U:Landroid/os/Handler;
invoke-direct {v0, v1}, Lcom/baidu/bus/d/i;-><init>(Landroid/os/Handler;)V
iput-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->A:Lcom/baidu/bus/d/i;
iget-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->A:Lcom/baidu/bus/d/i;
invoke-virtual {v0}, Lcom/baidu/bus/d/i;->a()V

翻译为Java代码就是:

this.A = new com.baidu.bus.d.i(this.U);
this.A.a();

上面的 U 是一个 com.baidu.bus.activity.bv 的对象,bv 是从 Handler 继承的类。首先看 A.a() 干了什么。

2. 在com.baidu.bus.d.i 类中找到 a() 方法,关键的代码是下面几行:

new-instance v0, Lcom/baidu/bus/net/b/a;
const-string v1, "city_list"
invoke-direct {v0, v1}, Lcom/baidu/bus/net/b/a;-><init>(Ljava/lang/String;)V
iput-object v0, p0, Lcom/baidu/bus/d/i;->c:Lcom/baidu/bus/net/b/a;
iget-object v0, p0, Lcom/baidu/bus/d/i;->c:Lcom/baidu/bus/net/b/a;
iget-object v1, p0, Lcom/baidu/bus/d/i;->e:Lcom/baidu/bus/net/a/c;
invoke-virtual {v0, v1}, Lcom/baidu/bus/net/b/a;->a(Lcom/baidu/a/a/k;)Z
sget-object v0, Lcom/baidu/bus/activity/App;->g:Lcom/baidu/a/a/l;
invoke-virtual {v0}, Lcom/baidu/a/a/l;->a()Lcom/baidu/a/a/h;
move-result-object v0
iget-object v1, p0, Lcom/baidu/bus/d/i;->c:Lcom/baidu/bus/net/b/a;
invoke-virtual {v0, v1}, Lcom/baidu/a/a/h;->c(Lcom/baidu/a/a/m;)Z

这几行的代码翻译为Java代码就是:

this.c = new com.baidu.bus.net.b.a("city_list");
this.c.a(this.e);
App.g.a().c(this.c);

在这里,e 成员是com.baidu.bus.net.a.c 的对象,查看这个类:

.class public abstract Lcom/baidu/bus/net/a/c;
.super Ljava/lang/Object;
.implements Lcom/baidu/a/a/k;
.implements Lcom/baidu/bus/net/a/a;

实现了两个接口类,分别是 com.baidu.a.a.k 和 com.baidu.bus.net.a.a,从构造函数中:

const-string v0, "<<ModelCallBack"
iput-object v0, p0, Lcom/baidu/bus/net/a/c;->b:Ljava/lang/String;

这两行代码是将类中的成员b赋了一个“<<ModelCallBack”的字符串,以此可以推测,这个类是HTTP收到 Response 后的回调。

3. 继续看 com.baidu.bus.net.b.a 类:

.class public final Lcom/baidu/bus/net/b/a;
.super Lcom/baidu/bus/base/f;

从 com.baidu.bus.base.f 类继承而来,没有 a() 方法,应该是父类的方法。但在这个类的构造函数里面可以看到:

const-string v0, "http://bs.baidu.com/offlinebusdata/prov_city_list.json"
iput-object v0, p0, Lcom/baidu/bus/net/b/a;->c:Ljava/lang/String;
iput-object p1, p0, Lcom/baidu/bus/net/b/a;->b:Ljava/lang/String;

翻译为Java代码是:

this.c = "http://bs.baidu.com/offlinebusdata/prov_city_list.json";
this.b = paramString;

这里的 paramString 就是传入的 city_list 字符串。在浏览器中查看this.c 的地址,下载了一个 prov_city_list.json 的文件,打开可以看到下面内容:

{
    "errorNo": 0, 
    "name": "中国", 
    "allList": [...],
    "hotCityList": [...]
}

在这里将城市列表隐去,在里面可以查看城市的 id 和 name。

 

4. 继续看com.baidu.bus.base.f 类:

.class public abstract Lcom/baidu/bus/base/f;
.super Lcom/baidu/a/a/m;

可以看到,父类是 com.baidu.a.a.m,在这个类里面有 a() 方法:

.method public final a()Ljava/lang/Object;
.method public final a(Ljava/lang/Object;)V
.method public final a(Ljava/lang/String;Ljava/lang/String;)V
.method public final declared-synchronized a(Lcom/baidu/a/a/k;)Z

有多个 a() 方法。这就是代码混淆后的迷惑作用。由于在 2 中传入的参数实现了 k 接口,因此要看最后一个方法,这个方法里调用了父类的 b() 方法:

invoke-super {p0, p1}, Lcom/baidu/a/a/m;->b(Lcom/baidu/a/a/k;)Z

 

5. 查看 com.baidu.a.a.m 类的 b() 方法,这个方法的关键代码就是下面这行:

iget-object v0, p0, Lcom/baidu/a/a/m;->g:Ljava/util/List;
invoke-interface {v0, p1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

把参数传过来的 com.baidu.a.a.k 对象添加到 g 成员里,g 就是一个 List. 因此 2 中的:

this.c.a(this.e);

就是向类成员 c (com.baidu.bus.net.b.a) 包含的一个成员中的 List 中添加了一个用于回调的 e (com.baidu.bus.net.a.c) 对象。接下来是下一行:

App.g.a().c(this.c);

无脑猜测,这行代码就是要执行c对象,也就是下载 http://bs.baidu.com/offlinebusdata/prov_city_list.json 这个链接了。

 

6. App.g 是一个 com.baidu.a.a.l 的对象,它的 a() 方法返回一个 com.baidu.a.a.h 的对象,查看 h 里面的 c() 方法。在这个方法中,可以看到下面代码:

new-instance v3, Lcom/baidu/a/a/c;
iget-object v4, p0, Lcom/baidu/a/a/h;->a:Lcom/baidu/a/a/l;
invoke-direct {v3, v4, p1}, Lcom/baidu/a/a/c;-><init>(Lcom/baidu/a/a/l;Lcom/baidu/a/a/m;)V

这是根据 this.a (com.baidu.a.a.h) 和传入的 com.baidu.a.a.m 对象构造了一个 com.baidu.a.a.c 的对象,然后调用了 c.c() 方法,返回 Boolean 类型:

invoke-virtual {v3}, Lcom/baidu/a/a/c;->c()Z

 

7. 查看 com.baidu.a.a.c 中的 c() 方法,这个方法包含下面代码:

new-instance v0, Lcom/baidu/a/a/d;
invoke-direct {v0, p0}, Lcom/baidu/a/a/d;-><init>(Lcom/baidu/a/a/c;)V
iput-object v0, p0, Lcom/baidu/a/a/c;->d:Lcom/baidu/a/a/d;
iget-object v0, p0, Lcom/baidu/a/a/c;->d:Lcom/baidu/a/a/d;
invoke-virtual {v0}, Lcom/baidu/a/a/d;->start()V

 

8. 查看 com.baidu.a.a.d 类:

.class final Lcom/baidu/a/a/d;
.super Ljava/lang/Thread;

从 Thread 派生出的类,那就查看 run() 方法,有下面两行代码:

iget-object v0, p0, Lcom/baidu/a/a/d;->a:Lcom/baidu/a/a/c;
invoke-virtual {v0}, Lcom/baidu/a/a/c;->d()Lorg/apache/http/HttpResponse;

在构造函数中可以看到,this.a 就是传入的 com.baidu.a.a.c 对象,在这里调用了它的 d() 方法,结果返回的是一个 HttpResponse 对象。

 

9. 查看 com.baidu.a.a.c 里面的 d() 方法,现在可以断定这个方法是发送HTTP请求,因此直接查找相关的代码:

new-instance v0, Lorg/apache/http/impl/client/DefaultHttpClient;
invoke-direct {v0, v1}, Lorg/apache/http/impl/client/DefaultHttpClient;-><init>(Lorg/apache/http/params/HttpParams;)V

构造了一个 DefaultHttpClient 对象,传入的参数暂时忽略。查看调用 execute 的地方:

iget-object v1, p0, Lcom/baidu/a/a/c;->a:Lcom/baidu/a/a/m;
invoke-virtual {v1}, Lcom/baidu/a/a/m;->b()Lorg/apache/http/client/methods/HttpUriRequest;
move-result-object v1
invoke-virtual {v0, v1}, Lorg/apache/http/impl/client/AbstractHttpClient;->execute(Lorg/apache/http/client/methods/HttpUriRequest;)Lorg/apache/http/HttpResponse;

在构造函数中可以看到,this.a 就是传入的第二个参数,一个 com.baidu.a.a.m 对象,回到第(6)中可以知道,这个对象是在(2)中构造的c对象:

this.c = new com.baidu.bus.net.b.a("city_list");

 

10. 查看 com.baidu.bus.net.b.a 的 b() 方法:

.method public final b()Lorg/apache/http/client/methods/HttpUriRequest;
    .locals 3
    new-instance v0, Lorg/apache/http/client/methods/HttpGet;
    iget-object v1, p0, Lcom/baidu/bus/net/b/a;->c:Ljava/lang/String;
    invoke-direct {v0, v1}, Lorg/apache/http/client/methods/HttpGet;-><init>(Ljava/lang/String;)V
    const-string v1, "Accept-Encoding"
    const-string v2, "gzip,deflate"
    invoke-interface {v0, v1, v2}, Lorg/apache/http/client/methods/HttpUriRequest;->addHeader(Ljava/lang/String;Ljava/lang/String;)V
    return-object v0
.end method

根据 this.c 成员(字符串)构造了一个HttpGet对象,添加了一个Header (Accept-Encoding: gzip,deflate),然后返回这个 HttpGet 对象。从(3)里可以知道,this.c 就是这个URL:
http://bs.baidu.com/offlinebusdata/prov_city_list.json

 

11. 这且还没有完,还没有解决下载后的数据如何解析,如何在 OfflineDataManageActivity 中展示。

12. 继续查看 com.baidu.a.a.d (从Thread类派生)类的 run() 方法,在取得 HttpResponse 之后,调用了两个静态方法:

iget-object v2, p0, Lcom/baidu/a/a/d;->a:Lcom/baidu/a/a/c;
iget-object v3, p0, Lcom/baidu/a/a/d;->a:Lcom/baidu/a/a/c;
invoke-static {v3}, Lcom/baidu/a/a/c;->c(Lcom/baidu/a/a/c;)Lcom/baidu/a/a/m;
move-result-object v3
invoke-virtual {v3}, Lcom/baidu/a/a/m;->e()I
invoke-static {v2, p0, v0}, Lcom/baidu/a/a/c;->a(Lcom/baidu/a/a/c;Lcom/baidu/a/a/d;Lorg/apache/http/HttpResponse;)V

查看 com.baidu.bus.a.a.c 类中的 a() 方法,调用了 HttpResponse 的 getEntity() ,获取 HttpEntity 之后调用了 getContent 获取了一个 InputStream,并调用 read() 读取了里面的内容,但是没有对 bytes 做任何处理。忽略。   

iget-object v1, p0, Lcom/baidu/a/a/d;->a:Lcom/baidu/a/a/c;
invoke-static {v1}, Lcom/baidu/a/a/c;->a(Lcom/baidu/a/a/c;)Lcom/baidu/a/a/e;
move-result-object v1
invoke-virtual {v1, v0}, Lcom/baidu/a/a/e;->a(Lorg/apache/http/HttpResponse;)V

调用 com.baidu.a.a.c 里的一个静态方法 a() 获取一个 com.baidu.a.a.e 对象,然后调用 e 对象的 a() 方法,传入 HttpResponse。

 

13. 查看 com.baidu.a.a.e 中的 a() 方法,这个方法是分发 HttpResponse,对成员 b (com.baidu.a.a.m) 中的一个 List 中包含的所有对象分发 HttpResponse。主要代码是下面两行:

iget-object v2, p0, Lcom/baidu/a/a/e;->b:Lcom/baidu/a/a/m;
invoke-interface {v0, v2, p1}, Lcom/baidu/a/a/k;->a(Lcom/baidu/a/a/m;Lorg/apache/http/HttpResponse;)V

上面的 v0 的类型是 com.baidu.a.a.k,是这样取得的:

iget-object v0, p0, Lcom/baidu/a/a/e;->b:Lcom/baidu/a/a/m;
invoke-virtual {v0}, Lcom/baidu/a/a/m;->c()Ljava/util/List;
move-result-object v0
invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator;
move-result-object v1
...
invoke-interface {v1}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v0
check-cast v0, Lcom/baidu/a/a/k;

List list = this.b.c();
iter = list.iterator();
v0 = iter.next();

成员 b 是一个 com.baidu.a.a.m 的对象,它的 c() 方法返回对象内的 g 成员,这是一个 List 的对象(ArrayList)。在 (5) 中我们知道,com.baidu.bus.d.i 向这个 List 里添加了一个元素,I 类中的 e (com.baidu.bus.net.a.c). 现在就调用它的 a() 方法:

v0.a(this.b, httpResponse);

同时我们知道,com.baidu.bus.net.a.c 实现了接口类 com.baidu.a.a.k,所以调用 k.a() 是完全没有问题的。

 

14. 接下来看 com.baidu.bus.net.a.c 类的 a() 方法,这个方法主要是读取 HttpResponse 中的数据,稍微特殊一点的地方就是,如果内容是压缩的(Content-Encoding: gzip),那么回调用com.baidu.bus.i.f 类的 a() 方法,看起来这个类是解压 gzip 的。获取到内容后,用UTF-8 进行解码,然后看下面代码:

new-instance v1, Landroid/os/Message;
invoke-direct {v1}, Landroid/os/Message;-><init>()V
const/4 v2, 0x4
iput v2, v1, Landroid/os/Message;->what:I
...
iput-object v2, v1, Landroid/os/Message;->obj:Ljava/lang/Object;

iget-object v0, p0, Lcom/baidu/bus/net/a/c;->a:Landroid/os/Handler;
invoke-virtual {v0, v1}, Landroid/os/Handler;->sendMessage(Landroid/os/Message;)Z

这里的 hObject 是一个 com.baidu.bus.net.a.h 的对象,h 类是一个简单的数据类,定义如下:

public final class h {
  public com.baidu.a.a.m a;
  public HttpResponse b;
  public com.baidu.bus.f.a c;
}

在 com.baidu.bus.net.a.c 类的 a() 方法中,只为h类中的 a 和 c 进行了赋值:

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
...
iput-object v0, v2, Lcom/baidu/bus/net/a/h;->c:Lcom/baidu/bus/f/a;
iput-object p1, v2, Lcom/baidu/bus/net/a/h;->a:Lcom/baidu/a/a/m;

上面的 m 就是向 a() 方法传递的第1个参数,类型是 com.baidu.a.a.m,即 com.baidu.a.a.e 类中的 b 成员。处理这个消息的 a 是 com.baidu.bus.net.a.d 类的对象。

 

15. 查看 com.baidu.bus.net.a.d 的 handleMessage() 方法,取出 Message 的 what 值,进入一个 switch 语句,上面传入的值是4,因此进入了 pswitch_3。

iget-object v1, p0, Lcom/baidu/bus/net/a/d;->a:Lcom/baidu/bus/net/a/c;
iget-object v2, v0, Lcom/baidu/bus/net/a/h;->c:Lcom/baidu/bus/f/a;
…
iget-object v0, v0, Lcom/baidu/bus/net/a/h;->a:Lcom/baidu/a/a/m;
invoke-virtual {v1, v2, v0}, Lcom/baidu/bus/net/a/c;->a(Lcom/baidu/bus/f/a;Lcom/baidu/a/a/m;)V

类里的 a 成员是一个 com.baidu.bus.net.a.c 的对象,它是在 com.baidu.bus.net.a.d 初始化时传入的参数;com.baidu.bus.net.a.d 初始化是在 com.baidu.bus.net.a.c 中的,传入的是 this 。因此接下来查看 com.baidu.bus.net.a.c 类的 a() 方法。

 

16. 查看 com.baidu.bus.net.a.c 类的 a() 方法,很可惜,没有找到合适的方法。观察到 c 类是一个抽象类,并且从 (2) 中得知,它的实例是 com.baidu.bus.d.i 类中的成员 e,查看 com.baidu.bus.d.i 类中 e 的初始化:

new-instance v0, Lcom/baidu/bus/d/j;
invoke-direct {v0, p0}, Lcom/baidu/bus/d/j;-><init>(Lcom/baidu/bus/d/i;)V
iput-object v0, p0, Lcom/baidu/bus/d/i;->e:Lcom/baidu/bus/net/a/c;

因此要从 com.baidu.bus.d.j 类中找相应的 a() 方法,查看com.baidu.bus.d.j 类:

.class final Lcom/baidu/bus/d/j;
.super Lcom/baidu/bus/net/a/c;

果然从 com.baidu.bus.net.a.c 继承,并且只有一个方法,正是我们要找的方法。

 

17. 查看 com.baidu.bus.d.j 中的 a() 方法,看到下面的代码:

iget-object v0, p0, Lcom/baidu/bus/d/j;->a:Lcom/baidu/bus/d/i;
check-cast p1, Lcom/baidu/bus/f/b;
invoke-static {v0, p1}, Lcom/baidu/bus/d/i;->a(Lcom/baidu/bus/d/i;Lcom/baidu/bus/f/b;)V

可以看到,调用了 com.baidu.bus.d.i 的静态方法 a(),第一个参数 this.a 是一个 com.baidu.bus.d.i 的对象,第二个参数是传入的第一个参数,即上面的 hObject.a,并转换为 com.baidu.bus.f.b 类型。查看 com.baidu.bus.d.i 中的 a() 方法,只是将传入的 paramA 赋给了类里面的成员d。回到 com.baidu.bus.d.j 中继续看下面的代码。然后,发送了一个Message:

new-instance v0, Landroid/os/Message;
invoke-direct {v0}, Landroid/os/Message;-><init>()V
const/16 v1, 0x3e9
iput v1, v0, Landroid/os/Message;->what:I
iget-object v1, p0, Lcom/baidu/bus/d/j;->a:Lcom/baidu/bus/d/i;
invoke-static {v1}, Lcom/baidu/bus/d/i;->a(Lcom/baidu/bus/d/i;)Lcom/baidu/bus/f/b;
move-result-object v1
iput-object v1, v0, Landroid/os/Message;->obj:Ljava/lang/Object;
iget-object v1, p0, Lcom/baidu/bus/d/j;->a:Lcom/baidu/bus/d/i;
invoke-static {v1}, Lcom/baidu/bus/d/i;->b(Lcom/baidu/bus/d/i;)Landroid/os/Handler;
move-result-object v1
invoke-virtual {v1, v0}, Landroid/os/Handler;->sendMessage(Landroid/os/Message;)Z

翻译为Java代码:

message = new Message();
message.what = 0x3e9; // 1001
message.obj = com.baidu.bus.d.i.a(this.a);
com.baidu.bus.d.i.b(this.a).sendMessage(message);

创建 Message,what 赋值为1001;查看 com.baidu.bus.d.i 中的 a() 方法(需要注意参数的类型和个数,混淆后的代码,有很多函数名是一样的,只能根据参数类型和个数区分),只是获取类里面的成员d(还记得刚才赋过值吗),其实就是刚才传入的 paramA,即这个函数内的第一个参数;然后调用 b() 方法,取得了类里面的 b 成员,它是个 Handler 对象,发送了创建的 Message。通过第1步我们知道,com.baidu.bus.d.i 里面的b成员,是 OfflineDataManageActivity 类里面的U成员,它是一个 com.baidu.bus.activity.bv 对象。

18. 查看 com.baidu.bus.activity.bv 类里面的 handleMessage() 方法,在 packed-switch 中正好有我们要找的 0x3e9 (1001),并且只有这一个分支:

.packed-switch 0x3e9
    :pswitch_0
.end packed-switch

在分支的代码中,找到这样的代码:

iget-object v0, p1, Landroid/os/Message;->obj:Ljava/lang/Object;
check-cast v0, Lcom/baidu/bus/f/b;
invoke-static {v1, v0}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->a(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/f/b;)V

 

19. 回到 OfflineDataManageActivity 中查看 a() 方法,直接把传入的对象赋给了 n 成员。再回到 com.baidu.bus.activity.bv 的 handleMessage() 方法中,接下来的代码:

invoke-static {v1}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->a(Lcom/baidu/bus/activity/OfflineDataManageActivity;)Lcom/baidu/bus/f/b;
move-result-object v1
invoke-static {v0, v1}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->b(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/f/b;)V

不用想,第一行的 a() 方法肯定是获取刚才赋过值的 n 成员,接下来看它的 b() 方法。

 

20. 查看 OfflineDataManageActivity 的 b() 方法,完整的代码如下:

new-instance v0, Lcom/baidu/bus/activity/ch;
invoke-direct {v0, p0}, Lcom/baidu/bus/activity/ch;-><init>(Lcom/baidu/bus/activity/OfflineDataManageActivity;)V
iput-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->B:Lcom/baidu/bus/activity/ch;
iget-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->B:Lcom/baidu/bus/activity/ch;
const/4 v1, 0x1
new-array v1, v1, [Lcom/baidu/bus/f/b;
const/4 v2, 0x0
aput-object p1, v1, v2
invoke-virtual {v0, v1}, Lcom/baidu/bus/activity/ch;->execute([Ljava/lang/Object;)Landroid/os/AsyncTask;
return-void

翻译为Java代码:

v0 = new com.baidu.bus.activity.ch(offlineDataManageActivity);
v0.B = offlineDataManageActivity;
v1 = new com.baidu.bus.f.b [] {p1};
v0.execute(v1);

上面的p1就是传入的 com.baidu.bus.f.b 对象。

 

21. 查看 com.baidu.bus.activity.ch 类,可以看到,它是从 AsyncTask 派生出来的:

.class final Lcom/baidu/bus/activity/ch;
.super Landroid/os/AsyncTask;

那么需要查看doInBackground() 方法,发现它直接调用了它的 a() 方法,查看 a() 方法。

 

22. 查看 com.baidu.bus.activity.ch 类里的 a() 方法,这个方法很长而且很无聊,大体就是将传入的 com.baidu.bus.f.b 对象中的 b 成员,附加到 ExpandableListAdapter 上,我们只要略微了解一下ExpandableListView 和 ExpandableLsitAdapter 的用法即可。此处,附加的过程略去,只要知道以下结果:
(1) OfflineDataManageActivity.L 保存省份,每个元素是 com.baidu.bus.b.f 的对象;
(2) com.baidu.bus.b.f 类里面有一个 List 类型的成员d,它保存着该省份下面的每个城市,每个元素都是 com.baidu.bus.b.a 的对象。
前面已经介绍了ExpandableView 如何响应点击事件,后面的过程就不赘述了。

posted @ 2016-01-19 11:42  西北望长安  阅读(415)  评论(0编辑  收藏  举报