• [原]百度公交离线数据格式分析——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 对象来执行下载任务。

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

  • 相关阅读:
    练字的感悟
    关于简单
    全都是泡沫
    跟着电影环游世界
    12.8《印度之行》
    11.21派生类对基类的访问
    Linux,begin
    如何利用google
    回调函数
    原型对象
  • 原文地址:https://www.cnblogs.com/zhangbaoqiang/p/5141601.html
Copyright © 2020-2023  润新知