[原]百度公交离线数据格式分析——2.从界面点击下载的流程

首先找到离线下载的界面(Activity),使用Apktool将APK包decode一下(Apktool的使用方法请参考官方文档)。这样decode之后生成的是源文件是.smali格式的,在这里也可以使用其他工具(如dex2jar+Java Decompiler或者Procyon)直接输出可读性更好的java文件,但是由于java的反编译或多或少存在一些问题,尤其对于inner class (delvik 中的 synthetic 方法)支持都不好,我就直接用smali了。可以看到,代码进行了简单的混淆。

1. 在 res/layout 下面找相应的 layout,可以看到,这些文件没有混淆,可以通过名字很容易的找到: offlinedata_manage_layout.xml.


2. 在 res/values/public.xml 中搜索“offlinedata_manage_layout”,找到对应的值 0x7f030024。


3. 在代码中搜索这个整数,0x7f030024 以及对应的 10 进制的值 2130903076,找到 com.baidu.bus.activity.OfflineDataManageActivity,在onCreate方法中(省略部分代码):

# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    const v0, 0x7f030024
    invoke-virtual {p0, v0}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->setContentView(I)V

4. 由 1 可知,在这个 layout 中只有一个 ExpandableListView,id 为 expandlistview_all_cities,对应的值为 0x7f06009f,在 OfflineDataManageActivity 中找到这个值使用的地方:

const v0, 0x7f06009f
invoke-virtual {p0, v0}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/ExpandableListView;
iput-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->z:Landroid/widget/ExpandableListView;

这段代码是查找 id 为 0x7f06009f 的 View,转换为 ExpandableListView 类型,并赋给 this.z 成员,类似:

this.z = (ExpandableListView) findViewById(0x7f06009f);

然后注意到 z.setAdapter() 的调用:

iget-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->z:Landroid/widget/ExpandableListView;
iget-object v1, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->y:Lcom/baidu/bus/activity/ck;
invoke-virtual {v0, v1}, Landroid/widget/ExpandableListView;->setAdapter(Landroid/widget/ExpandableListAdapter;)V

这段代码翻译一下就是:

this.z.setAdapter(this.y);

同时得知y的类型是com.baidu.bus.activity.ck,于是找到这个文件。

5. 在com/baidu/bus/activity/ck.smali 中开头两行可以看到:

.class final Lcom/baidu/bus/activity/ck;
.super Landroid/widget/BaseExpandableListAdapter;

这个类是从 BaseExpandableListAdapter 派生而来,于是可以断定这就是我们要找的类。在 getChildView() 中:

iget-object v1, p0, Lcom/baidu/bus/activity/ck;->a:Lcom/baidu/bus/activity/OfflineDataManageActivity;
invoke-static {v1}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->d(Lcom/baidu/bus/activity/OfflineDataManageActivity;)Landroid/view/LayoutInflater;
move-result-object v1
const v2, 0x7f030020
const/4 v3, 0x0
invoke-virtual {v1, v2, v3}, Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;)Landroid/view/View;

这段代码是调用 OfflineDataManageActivity 的一个静态方法 d() 获取一个 LayoutInflater 的实例,然后调用 inflate 找到 id 为 0x7f030020 的 layout,在 res/values/public.xml 中查找这个 id,发现是 offlinedata_manage_city_item。做了一系列动作(主要是创建了一个 com.baidu.bus.activity.cf,并且将 offlinedata_manage_city_item 上的各个控件赋给了 cf 的成员)之后,调用了下面一个方法:

invoke-virtual {v1, v2, v3}, Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;)Landroid/view/View;
move-result-object p4
...
iget-object v2, p0, Lcom/baidu/bus/activity/ck;->a:Lcom/baidu/bus/activity/OfflineDataManageActivity;
invoke-static {v2, v0, p4}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->a(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/b/a;Landroid/view/View;)Z

在 OfflineDataManagerActivity 中,对应的 a 方法是一个 synthetic 的:

.method static synthetic a(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/b/a;Landroid/view/View;)Z
    .locals 1
    const/4 v0, 0x1
    invoke-direct {p0, p1, p2, v0}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->a(Lcom/baidu/bus/b/a;Landroid/view/View;Z)Z
    move-result v0
    return v0
.end method

这段代码翻译后就是:

static boolean synthetic a(OfflineDataManageActivity activity, com.baidu.bus.b.a parama, View view) 
{
    return activity.a(parama, view, true);
}

可以看到,调用的 a() 方法是 private 的:

.method private a(Lcom/baidu/bus/b/a;Landroid/view/View;Z)Z

在这个方法里,找到这段代码:

const v6, 0x7f060094
invoke-virtual {p2, v6}, Landroid/view/View;->findViewById(I)Landroid/view/View;
move-result-object v6
check-cast v6, Landroid/widget/FrameLayout;
......
new-instance v0, Lcom/baidu/bus/activity/ca;
invoke-direct {v0, p0}, Lcom/baidu/bus/activity/ca;-><init>(Lcom/baidu/bus/activity/OfflineDataManageActivity;)V
invoke-virtual {v6, v0}, Landroid/widget/FrameLayout;->setOnClickListener(Landroid/view/View$OnClickListener;)V

这段代码翻译后就是这样的:

FrameLayout frame = (FrameLayout) view.findViewById(0x7f060094);
frame.setOnClickListener(new ca(this));

在 res/values/public.xml 中可以看到,0x7f060094 对应的 id 是 fl_op,它在 res/layout/offlinedata_manage_city_item.xml 里面,这个 FrameLayout 有3个 ImageView,对应的图形分别是:

  start_download

  stop_download

  continue_download

6. 接下来分析 com.baidu.bus.activity.ca 类,在前几行可以看到:

.class final Lcom/baidu/bus/activity/ca;
.super Ljava/lang/Object;
.implements Landroid/view/View$OnClickListener;

这个类实现了 View.OnClickListener 的接口,接下来就看 onClick() 方法中干了什么。找到

invoke-virtual {p1}, Landroid/view/View;->getTag()Ljava/lang/Object;
move-result-object v0
...
iget v2, v0, Lcom/baidu/bus/b/a;->f:I
packed-switch v2, :pswitch_data_0

这里出现了一个 switch,查看 table - pswitch_data_0 :

:pswitch_data_0
.packed-switch 0x0
    :pswitch_0
    :pswitch_1
    :pswitch_2
    :pswitch_3
    :pswitch_0
.end packed-switch

只有4种可能,0-3. pswitch_0 的代码是这样的:

:pswitch_0
return-void

看来是个非法的值,所以default也会执行到这里。再观察1-3的分支,可以看到,2和3都调用了 com.baidu.bus.i.f->a(ApplicationContext, message, 0) 这个方法。2的调用:

const-string v3, "u505cu6b62u4e0bu8f7d"
const/4 v4, 0x0
invoke-static {v2, v3, v4}, Lcom/baidu/bus/i/f;->a(Landroid/content/Context;Ljava/lang/CharSequence;I)V

3的调用:

const-string v3, "u7ee7u7eedu4e0bu8f7d"
const/4 v4, 0x0
invoke-static {v2, v3, v4}, Lcom/baidu/bus/i/f;->a(Landroid/content/Context;Ljava/lang/CharSequence;I)V

看看这两个字符串分别是“停止下载”和“继续下载”,那么推测1应该就是“开始下载”。1-3的功能暂时这样猜测。再看1的分支调用的函数:

:pswitch_1
iget-object v2, p0, Lcom/baidu/bus/activity/ca;->a:Lcom/baidu/bus/activity/OfflineDataManageActivity;
invoke-static {v2, v0, v6, v7, v1}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->a(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/b/a;Lcom/baidu/bus/activity/cl;Lcom/baidu/bus/activity/cl;Lcom/baidu/bus/activity/cl;)V

翻译一下就是:

OfflineDataManageActivity.a(this.a, view.getTag(), cl_1, cl_2, cl_3);

注意到后面有3个 cl 类的对象作为参数,从 cl 类的定义:

final class cl {
  ProgressBar a = null;
  TextView b = null;
  ImageView c = null;
  ImageView d = null;
  ImageView e = null;
}

来看,这个类和下载的 FrameLayout 对应。接下来要回到 OfflineDataManageActivity 中查看调用的这个 a() 方法。

7. 在 OfflineDataManageActivity 中找到这个 a() 方法:

.method static synthetic a(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/b/a;Lcom/baidu/bus/activity/cl;Lcom/baidu/bus/activity/cl;Lcom/baidu/bus/activity/cl;)V

发现它也是一个 synthetic 的。在做了一系列动作之后,构造了一个com.baidu.bus.base.a 对象,com.baidu.bus.base.a 包含一个对话框,然后调用了这个对象的 a() 方法来构造并显示一个对话框:

new-instance v0, Lcom/baidu/bus/base/a;
invoke-direct/range {v0 .. v6}, Lcom/baidu/bus/base/a;-><init>(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
new-instance v1, Lcom/baidu/bus/activity/cb;
invoke-direct {v1, p0, v0}, Lcom/baidu/bus/activity/cb;-><init>(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/base/a;)V
invoke-virtual {v0, v1}, Lcom/baidu/bus/base/a;->a(Landroid/view/View$OnClickListener;)Lcom/baidu/bus/base/a;
invoke-virtual {v0}, Lcom/baidu/bus/base/a;->a()Landroid/app/Dialog;

这段代码翻译一下是(方法名的翻译不一定对):

dialog = new com.baidu.bus.base.a(context, titleText, messageText, bgResid, leftButtonText, rightButtonText);
listener = new com.baidu.bus.activity.cb(OfflineDataManageActivity.this, dialog);
dialog.setButtonOnClickListener(listener);
dialog.show();

所以要看点击“确定”后干了什么,查看 cb.onClick() 方法。

8. 在 com.baidu.bus.activity.cb 类中的 onClick() 方法中,有如下代码:

iget-object v0, p0, Lcom/baidu/bus/activity/cb;->a:Lcom/baidu/bus/activity/OfflineDataManageActivity;
iget-object v1, p0, Lcom/baidu/bus/activity/cb;->a:Lcom/baidu/bus/activity/OfflineDataManageActivity;
invoke-static {v1}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->u(Lcom/baidu/bus/activity/OfflineDataManageActivity;)Lcom/baidu/bus/b/a;
move-result-object v1
invoke-static {v0, v1}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->e(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/b/a;)V

翻译为Java代码:

OfflineDataManageActivity.e(this.a, OfflineDataManageActivity.u(this.a));

9. 查看OfflineDataManageActivity.e() 方法干了什么,e() 方法全部如下:

.method static synthetic e(Lcom/baidu/bus/activity/OfflineDataManageActivity;Lcom/baidu/bus/b/a;)V
    .locals 3
    new-instance v0, Lcom/baidu/bus/activity/cn;
    invoke-direct {v0, p0}, Lcom/baidu/bus/activity/cn;-><init>(Lcom/baidu/bus/activity/OfflineDataManageActivity;)V
    iput-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->G:Lcom/baidu/bus/activity/cn;
    iget-object v0, p0, Lcom/baidu/bus/activity/OfflineDataManageActivity;->G:Lcom/baidu/bus/activity/cn;
    const/4 v1, 0x1
    new-array v1, v1, [Lcom/baidu/bus/b/a;
    const/4 v2, 0x0
    aput-object p1, v1, v2
    invoke-virtual {v0, v1}, Lcom/baidu/bus/activity/cn;->execute([Ljava/lang/Object;)Landroid/os/AsyncTask;

    return-void
.end method

翻译为Java代码:

static void synthetic e(OfflineDataManageActivity activity, com.baidu.bus.b.a city)
{
    activity.G = new com.baidu.bus.activity.cn(activity);
    activity.G.execute(new com.baidu.bus.b.a[] {city});
}

com.baidu.bus.activity.cn 是从AsyncTask继承的类,它的 doInBackground() 方法调用了内部的 a() 方法:

invoke-direct {p0, p1}, Lcom/baidu/bus/activity/cn;->a([Lcom/baidu/bus/b/a;)Ljava/lang/Boolean;

在a() 方法中,首先创建一个Intent,然后调用 startService() 进行后台下载:

new-instance v0, Landroid/content/Intent;
invoke-direct {v0}, Landroid/content/Intent;-><init>()V
iget-object v1, p0, Lcom/baidu/bus/activity/cn;->a:Lcom/baidu/bus/activity/OfflineDataManageActivity;
invoke-virtual {v1}, Lcom/baidu/bus/activity/OfflineDataManageActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v1
const-class v3, Lcom/baidu/bus/service/UpdateService;
invoke-virtual {v0, v1, v3}, Landroid/content/Intent;->setClass(Landroid/content/Context;Ljava/lang/Class;)Landroid/content/Intent;

这段代码翻译为 Java 代码就是:

intent = new Intent();
intent.setClass(this.a.getApplicationContext(), UpdateService.class);

这个 intent 调用了两次 putExtra() :

intent.putExtra("checkType", "checkSingleUpdateDownload");
intent.putExtra("downloadRecord", downloadRecord);

这里使用的 downloadRecord 类的定义为:

public class DownloadRecord implements Serializable {
    public String MD5;
    public String cityId;
    public String cityName;
    public String description;
    public String downLoadPath;
    public int engineVersion;
    public int id;
    public String localPath;
    public long size;
    public long version;
}

具体实例的内容是通过 com.baidu.bus.b.a 类中的 h 对象获取的:

public final class a {
    public int a;
    public String b;
    public String c;
    public int d = -1;
    public long e = -1L;
    public int f = 0;
    public int g = -1;
    public DataUpdateInfo h = null;
}

10. 在 startService() 调用之后,进入到 com.baidu.bus.service.UpdateService 中,这个类继承了 android 的 Service 类;观察它的代码还可以看到,在收到命令后,构造了一个 FutureTask 对象来执行下载任务。

至此,已经看到了从界面到下载的大体流程,但还没有看到下载的链接是如何生成的。接下来的任务就是回顾这个流程,以寻找下载链接。

原文地址:https://www.cnblogs.com/zhangbaoqiang/p/5141601.html