首先找到离线下载的界面(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, "\u505c\u6b62\u4e0b\u8f7d" 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, "\u7ee7\u7eed\u4e0b\u8f7d" 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 对象来执行下载任务。
至此,已经看到了从界面到下载的大体流程,但还没有看到下载的链接是如何生成的。接下来的任务就是回顾这个流程,以寻找下载链接。