IPC-Android中的进程与进程间通信

一、Android中的进程

官方解释
Android的系统架构底层是Linux,上层采用的是可以运行java(其实是dex字节码)的虚拟机(Dalvik & ART),Dalvik的核心内容是实现库(libdvm.so),大体由C语言实现。依赖于Linux内核的一部分功能——线程机制、内存管理机制,能高效使用内存,并在低速CPU上表现出的高性能。每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。

1. 内核进程(确切的说是内核线程kernel thread)和用户进程

这个是根据进程运行空间来区分的。linux常见进程与内核线程

adb ps 命令的带的

上图是用ChromeADB这个工具抓取的系统进程信息,当然也可直接用adb 的ps命名查看。对上图表头的做些解释,PID指的是进程id,PPID指的是parent ID也就是父进程的pid。至于VSIZE和RSS是和内存相关的,暂时不管。仔细观察pid和ppid会发现规律。

1.0. 进程这东西要从Linux说起,Android系统内核是Linux,所以Android系统启动,必须先要Linux系统启动。而Linux下有3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2),至于这三个进程的作用,可以简答的理解为idle(PID=0)的这个是操作系统boot起来后第一个线程,initkthreadd都是由它通过kernel_thread创建出来的。
1.1. kthreadd线程由idle通过kernel_thread创建,并始终运行在内核空间,它的任务就是管理和调度其他内核线程kernel_thread(内核线程)。所有内核进程的用户都是root,所有的内核线程都是直接或者间接的以kthreadd为父进程 。
1.2. init进程:由idle通过kernel_thread创建,在Linux中的定义是一个由内核启动的用户级进程, Kerner启动后会调用/system/core/init/Init.cpp的main()方法,然后解析/init.rc文件,包括所有的.rc文件,最重要的Zygote进程启动起来。所以,init始终是第一个进程(其进程编号始终为1)。在系统启动完成完成后,init将变为守护进程监视系统其他进程。在Android中的含义也是一样的,是所有用户进程的父进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
//主要进程的创建(kernel_thread或者fork)关系,PID给出的那几个的PID在任何设备上都是相同的
idle(PID == 0)
|---init(PID==1)
|---servicemanager(它是整个Binder机制的守护进程)
|---zygote
|---system_server
|---com.xxx.app(每个app至少有一个进程)
|---mediaserver
|--- 。。。其他进程
|---kthreadd(PID == 2)
|---binder
|---watchdog
|---。。。其他进程

其实对于Android中进程,我们主要关心的是init进程fork出来的那几个重要的进程

2.进程生命周期

看官网解释很清楚,不再抄过来了
Android进程的重要性,划分5级,前台进程的重要性最高,依次递减:
前台进程(Foreground process)
可见进程(Visible process)
服务进程(Service process)
后台进程(Background process)
空进程(Empty process)

作用:必要时(当资源有限时),系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

3. Android中开启进程的方式

  • Manifest文件给<activity> <service> <receiver> <provider>设置属性android:process 指定单独进程

    1
    2
    3
    4
    <!-- 假设包名为me.febsky.app -->
    <activity
    android:name="me.febsky.app.OtherActivity"
    android:process=":remote" />
  • 通过jni在native层fork一个进程出来

注:android:process=":remote" 这行代码翻译一下,其实他的完整进程名为:me.febsky.app:remote(假设当前应用包名为me.febsky.app)。进程名以“:”开头的进程属于当前应用的私有进程,其他应用不可以通过SharedUID的方式和他跑在同一个进程中;而如果没有用“:”,直接指定android:process="me.febsky.remote" 说明开启的这个进程是可以和别的app(这个app必须和当前app签名相同并且指定了sharedUID)共享的。

二、Android中IPC

一个app可以开启多个进程,那么多个进程间就有可能需要通信。IPC是Inter-Process Communication(进程间通信)的首字母缩小。

1. Linux 中一些经典的IPC

Android系统是基于Linux内核的,而Linux内核继承和兼容了丰富的Unix系统进程间通信(IPC)机制。有传统的管道(Pipe)、信号(Signal)和跟踪(Trace),这三项通信手段只能用于父进程与子进程之间,或者兄弟进程之间;后来又增加了命令管道(Named Pipe),使得进程间通信不再局限于父子进程或者兄弟进程之间;为了更好地支持商业应用中的事务处理,在AT&T的Unix系统V中,又增加了三种称为“System V IPC”的进程间通信机制,分别是报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore);后来BSD Unix对“System V IPC”机制进行了重要的扩充,提供了一种称为插口(Socket)的进程间通信机制

Socket:这个东西,在学java网络编程的时候接触的。它在Android中有个经典的使用场景,在启动一个单独进程中的Activity或者Service的时候(Activity和Service的启动后面再说,这里只需知道他们都是通过AMS启动的就行了),ActivityManagerService会通过Socket进程间通信机制,通知Zygote进程为这个应用程序创建一个新的进程。

2. Android上经典的IPC Binder

进程隔离

要说IPC,先要知道进程隔离的概念
wiki解释:进程隔离是为保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术。这个技术是为了避免进程A写入进程B的情况发生。 进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防止进程A将数据信息写入进程B。

Binder

这个东西并不是Google搞出来的,Binder的前身是OpenBinder,而OpenBinder的作者Dianne Hackborn现在在Google工作,负责Android平台的开发工作。

Binder主要由Client(Client进程)、Server(Server进程)、ServiceManager和Binder驱动程序组成。其中Client、Service和ServiceManager运行在用户空间,而Binder驱动程序运行在内核空间。核心组件就是 Binder驱动程序了,而ServiceManager提供辅助管理的功能,类似于DNS的作用,无论是Client还是Service进行通信前首先要和ServiceManager取得联系。而ServiceManager是一个守护进程,负责管理Server并向Client提供查询Server的功能。

这里面很重要的一点是Binder驱动,进程a与进程b通信实际上是进程a先于Binder驱动通信,然后Binder驱动再与进程b通信的一个过程。其实进程a或者进程b与Binder的通信也是进程间通信,不过这个Binder驱动是个特殊的进程,他运行在内核空间,至于Binder怎么就搞到了内核空间里面去了可以搜索(Linux的动态可加载内核模块(Loadable Kernel Module,LKM))。由于进程隔离的存在,进程a打死都不可能访问到进程b中的内存的,也就不能进行不能调用进程b中的方法,但是进程a可以通过系统调用的方式访问Binder进程(内核进程),而内核空间的进程能访问用户进程a或者进程b。了解下Linux内核控件和用户控件的概念

ServiceManager是由init进程通过解析init.rc文件而创建的,其所对应的可执行程序/system/bin/servicemanager,所对应的源文件是service_manager.c,进程名为/system/bin/servicemanager。可以把/init.rc文件导出来看下,没什么神秘的,创建这个进程的指令为:

1
2
3
4
5
6
7
8
9
10
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm

Mac下Android源码(AOSP)编译环境搭建方法

一、编译源码的背景环境

Android源码编译有什么困难

  1. AOSP 非常庞大,需要下载,但是他是Google家的,和大陆开发者之间隔着一个GFW
  2. 官方文档 推荐使用Ubuntu 14.04进行编译。我用的是MacOS,官网也给了Mac下的编译方式,不过看起来还是挺麻烦的。而且我怕搞乱我的电脑。

针对上面的两点,以前有这么两种解决方式,以前有这么几种解决方式

  1. 至于下载慢,可以使用国内镜像清华镜像科大镜像,好像现在google的开发者网站部分不用梯子就可以访问了[当前时间2017-04]
  2. 有人使用虚拟机,然后安装Ubuntu,然后编译源码。这方式也可以,但是Mac下三种虚拟机,免费的那个还不好用。而且编译本身就比较消耗资源,再装个虚拟机就更麻烦了

后来有人提出用Docker

而且这个人搞了一份Docker镜像出来 kylemanna/docker-aosp,后来有人针对Docker以及天朝的网络环境做了一部分修改,fork了一份国产的 tiann/docker-aosp。主要是修改了shell脚本中的下载源。但是这个东西在MAC OS上有bugissues 19所以我就没用成功。

有一点题外话需要说一下,这个Docker安装后会开机默认启动,默认启动倒是也没问题,有问题的是他不可以和Android虚拟机同时运行,就是AndroidStudio自带的那个虚拟机。

二、搭建Mac编译环境

修改文件描述符限制
~/.bash_profile中添加以下内容

the number of open files to be 1024
1
ulimit -S -n 1024

开发环境要求

  1. Mac OS v10.10 (Yosemite) 或以上,安装了 Xcode 4.5.2或以上,安装了Command Line Tools。
  2. 安装JDK 7
  3. 安装了python 2.7
  4. GNU Make 3.81或3.82
  5. Git

我本机环境

  1. Mac OS v10.12.4
  2. xcode v8.0
  3. git version 2.8.4 (Apple Git-73)
  4. GNU Make 3.81
  5. 为了编译不同版本的Android源码,同时安装了JDK7 & JDK8,通过配置.bash_profile 来切换
  6. Python 2.7.11
  7. 之前我电脑还安装了homebrew

磁盘分区

为什么在Mac上编译会很麻烦呢,因为Mac的文件系统默认不区分大小写,但是Android源码编译系统的要求区分大小写,这时候我们可以创建个区分大小写的磁盘空间。方法如下:

  1. 打开磁盘工具(在Launchpad中直接搜索就行)
  2. 如下图操作(磁盘命名为AOSP,磁盘大小为100G,磁盘格式一定要选择区分大小写) 首先选中Macintosh HD 然后Comand + N

磁盘分区

这个磁盘分区的名字后面要用到,磁盘大小好像官网推荐70G,以防不够还是大点吧,格式一定要选区分大小写。这个磁盘分区还有另一种方式,我不知道这两种方式到底有什么区别。对应磁盘的分区使用,有人为了节省Mac上的硬盘,也为了防止搞坏系统,用了外接硬盘的方式然后操作这个外接硬盘的分区。

下载Android源码

下面是科大的源也可以用清华的参考文献我下载的是Android6.0的源码,最终编译的时候选择的是x86_64

  1. 下载 repo 工具:

    1
    2
    3
    4
    mkdir ~/bin
    PATH=~/bin:$PATH
    curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
    chmod a+x ~/bin/repo
  2. 建立工作目录:

    1
    2
    3
    cd /Volumes/AOSP/
    mkdir WORKING_DIRECTORY
    cd WORKING_DIRECTORY
  3. 初始化仓库

    1
    2
    3
    repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest
    ## 如果提示无法连接到 gerrit.googlesource.com,可以编辑 ~/bin/repo,把 REPO_URL 一行替换成下面的:
    ## REPO_URL = 'https://gerrit-googlesource.proxy.ustclug.org/git-repo'

    如果需要某个特定的 Android 版本(Android 版本列表),可以通过下面的方式指定版本号,如果不指定则代码下载当前最新的代码。

    1
    repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b android-6.0.1_r46

    注:在mac上运行这行可能会报错,GnuPG 这玩意不可用,解决方式看最后错解决

  4. 同步源码树(以后只需执行这条命令来同步)如果中途出现网络错误,可以停止后在执行下面的命令:

1
repo sync

然后就等着吧0_0

编译源码

  1. 清空缓存
    $ make clobber

    注:如果运行这行命令出错看文章最后错误处理

  2. 配置编译环境
    $ source build/envsetup.sh
  3. 选择编译目标
    $ lunch 这时候会列出一系列的选项然后选择一个如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    You're building on Darwin
    Lunch menu... pick a combo:
    1. aosp_arm-eng
    2. aosp_arm64-eng
    3. aosp_mips-eng
    4. aosp_mips64-eng
    5. aosp_x86-eng
    6. aosp_x86_64-eng
    7. full_fugu-userdebug
    8. aosp_fugu-userdebug
    9. mini_emulator_arm64-userdebug
    10. m_e_arm-userdebug
    11. m_e_mips-userdebug
    12. m_e_mips64-eng
    13. mini_emulator_x86-userdebug
    14. mini_emulator_x86_64-userdebug
    15. aosp_dragon-userdebug
    16. aosp_dragon-eng
    17. aosp_marlin-userdebug
    18. aosp_sailfish-userdebug
    19. aosp_flounder-userdebug
    20. aosp_angler-userdebug
    21. aosp_bullhead-userdebug
    22. hikey-userdebug
    23. hikey960-userdebug
    24. aosp_shamu-userdebug
    Which would you like? [aosp_arm-eng]

    然后直接输入上面选项的序号就好了这里,我选择第一个,至于上面那些名字的含义是啥看下面解释:

    官网说根据上面名字的后缀可以判断要编译那种类型的:

    • user: limited access; suited for production(有限的访问权限,一般用于发布版)
    • userdebug: 这个和user类似,但是可以获取root权限,并且能够调试
    • eng: 具有开发配置,并且有额外的调试工具(注:工程师模式engineer)

    lunch命令也可以直接这么用 $ lunch aosp_arm-eng

  4. 开始编译
    make -j16 这里指定的开启几个并行task来进行编译,官网建议j后面的数字根据你电脑的线程数量的1~2倍来设置,比如电脑是双核CPU,每个CPU为双核心四线程,那么就有8个线程。所以我配置的数量为16。
    然后就是漫长的等待ing…

启动模拟器

  1. $ source build/envsetup.sh
  2. $ lunch (选择刚才你设置的目标版本,我编译的是x86_64-eng的那个)
  3. $ emulator模拟器启动,这个命令有很多复杂用法,具体可参考官网。

烧录镜像到机器中

这个烧录过程我没有测试,官网是这么写的。

  1. 进入fastboot模式
    $ adb reboot bootloader
  2. 把img文件刷进去
    $ fastboot flashall -w 这个 -w 是为了wipes the /data partition擦除/data分区

    驱动下载

AndroidStudio中查看源码

在android源码中有这么一个目录development/tools/idegen。
顾名思义,是生成ide的project文件,主要是生成intellij的project文件,可用于AndroidStudio。

  1. 在整个Android源码全编成功之后,然后编译idegen模块,用以生成Android studio的工程配置文件,编译成功之后就生成了idegen.jar(out/host/darwin-x86/framework/idegen.jar),运行如下命令:
    $ mmm development/tools/idegen/

    注:如果刚才编译AOSP的那个命令行窗口关闭了,必须要在执行source build/envsetup.sh一次,用了初始化编译环境。

  2. 在源码根目录生成对应的android.ipr、android.iml IEDA工程配置文件。以便于AndroidStudio可以打开项目
    $ . development/tools/idegen/idegen.sh
  3. 打开Android studio,点击File –> Open,选择刚刚生成的android.ipr就好了。

三、错误处理

执行repo init的错误

错误1描述

1
2
warning: gpg (GnuPG) is not available.
warning: Installing it is strongly encouraged.

解决方法:
原因是GnuPG for osx没有安装,去https://www.gnupg.org/download/下载安装即可。
我用homebrew安装的brew install gnupg

执行make clobber的错误

错误1描述:

1
2
build/core/config.mk:660: error: Error: could not find jdk tools.jar at /System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/../lib/tools.jar, please check if your JDK was installed correctly.
17:29:26 ckati failed with: exit status 1

解决方式:
~/.bash_profile添加如下代码

1
2
3
4
export JAVA_8_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home
export JAVA_HOME=$JAVA_8_HOME
##上面是jdk的配置,主要是添加下面这一行
export ANDROID_JAVA_HOME=$JAVA_HOME

错误2描述

1
2
3
4
build/core/combo/mac_version.mk:38: *****************************************************
build/core/combo/mac_version.mk:39: * Can not find SDK 10.6 at /Developer/SDKs/MacOSX10.6.sdk
build/core/combo/mac_version.mk:40: *****************************************************
build/core/combo/mac_version.mk:41: *** Stop.. Stop.

解决方法:
出现这个错误是因为xcode的版本不对,首先查看本机安装xcode的版本,
$ cd /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
然后修改build/core/combo/mac_version.mk这个文件中的下面这行,把本机的版本号添加到下面,我的是10.12 添加后如下:

1
mac_sdk_versions_supported :=  10.6 10.7 10.8 10.9 10.12

错误3描述:

1
2
3
4
5
6
7
system/core/libcutils/threads.c:38:10: error: 'syscall' is deprecated: first deprecated in OS X 10.12 - syscall(2) is unsupported; please switch to a supported interface. For SYS_kdebug_trace use kdebug_signpost(). [-Werror,-Wdeprecated-declarations]
return syscall(SYS_thread_selfid);
^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/unistd.h:733:6: note: 'syscall' has been explicitly marked deprecated here
int syscall(int, ...);
^
1 error generated.

解决方式:
这个错误3归根到底是因为错误2引起的,再归根下是xcode版本引起的,(为啥说在mac上编译麻烦,这个xcode版本会出这么多破事),没办法这个我们上面用的mac sdk 10.12 版本太高了,这个版本废弃了某些方法导致编译不可用,那就去下个低版本的吧传送门压缩包在这然后下载那个10.11。理论上来说,下载后把目录放到这个路径下/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/和这个MacOSX10.12.sdk目录平级就行了,然后把错误2中添加到build/core/combo/mac_version.mk中的那个10.12改成10.11就好了。但是这个东西貌似在下次xcode升级的时候会被删掉。
a oh so? 弄个软连接:
$ mkdir ~/lib
然后把下载的压缩包解压到~/lib
$ sudo ln -s ~/lib/MacOSX10.11.sdk /Applications/XCode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk

我的Android混淆(Proguard文件)是怎么写的

当前环境

Android Studio: 3.2.1
Gradle 插件: ‘com.android.tools.build:gradle:3.2.1’
Gradle : gradle-4.6

具体配置

因为proguard文件要靠手写,所以有时候感觉很难,但是找到规律之后也挺简单的,感觉难是因为,不熟悉它的一些关键字,这里列出了常用的关键字,以及人能看懂的含义,其实要想看明白,自己写的Demo测试一下就好了,借助于反编译工具看下到底什么被混淆了,工具看这。我下面写了详细的注释github,常用的也就这么多了,其余的可以根据命令行进行调试,然后逐个添加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#-ignorewarnings                     # 忽略警告,避免打包时某些警告出现
-optimizationpasses 5 # 指定代码的压缩级别
-dontusemixedcaseclassnames # 是否使用大小写混合 混淆时不会产生形形色色的类名
-dontskipnonpubliclibraryclasses # 是否混淆第三方jar
-dontpreverify # 混淆时是否做预校验
-verbose # 混淆时是否记录日志
-dontoptimize # 不优化输入的类文件

-keepattributes *Annotation*, SourceFile, InnerClasses, LineNumberTable, Signature, EnclosingMethod
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #优化 混淆时采用的算法

-keep public class * extends android.app.Activity # 未指定成员,仅仅保持类名不被混淆
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.app.View
-keep public class * extends android.app.IntentService
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.hardware.display.DisplayManager
-keep public class * extends android.os.UserManager
-keep public class com.android.vending.licensing.ILicensingService
-keep public class * extends android.app.Fragment

-keep public class * extends android.support.v4.widget
# *匹配任意字符不包括 “ . ” ,**匹配任意字符
# 下面这行的意思是,保留v4下面的类名及其v4下面子包的类名不被混淆
-keep public class * extends android.support.v4.**
# 下面 这行表示一个接口中的所有的东西都不被混淆
-keep interface android.support.v4.app.** { *; }
# 下面这行表示保持这个包及其子包下面的所有类的类名及其类里面的所有内容都不混淆
-keep class android.support.v4.** { *; }
-keep class android.os.**{*;}
-keep class android.support.v8.renderscript.** { *; }

-keep class **.R$* { *; }
-keep class **.R{ *; }

#实现了android.os.Parcelable接口类的任何类,以及其内部定义的Creator内部类类型的public final静态成员变量,都不能被混淆和删除
-keep class * implements android.os.Parcelable { # 保持Parcelable不被混淆
public static final android.os.Parcelable$Creator *;
}

#keepclasseswithmembernames 保留类和类中的成员,防止被混淆,保留指明的成员,成员没有引用会被移除
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}

#keepclasseswithmembers 保留类和类中的成员,防止被混淆或移除,保留指明的成员
# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * { # 保持自定义控件类不被混淆
public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int, int);
}

-keepclassmembers class * extends android.app.Activity { #保持类成员
public void *(android.view.View);
}

-keepclassmembers class * extends android.content.Context {
public void *(android.view.View);
public void *(android.view.MenuItem);
}

-keepclassmembers enum * { # 保持枚举 enum 类不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}

# Explicitly preserve all serialization members. The Serializable interface
# is only a marker interface, so it wouldn't save them.
-keepnames class * implements java.io.Serializable

-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}

#####################上面这些是系统默认配置的#####################3
## 一般来说上面的这些即使我们不写,开了混淆也会自动启用
## 现在我用的 Android Studio Gradle的插件是 3.2.1 这个插件自带默认混淆文件
## 在启用混淆的时候解压出来,详情参考 /项目名/build/intermediates/proguard-files/proguard-android.txt-3.2.1 里面的注释
## 注释上说gradle 的 Android 插件,从2.2 版本开始,不再使用sdk本地的Proguard,而以后gradle插件自带默认Proguard配置文件


#-libraryjars libs/jar包名字.jar #缺省proguard 会检查每一个引用是否正确,但是第三方库里面往往有些不会用到的类,没有正确引用。如果不配置的话,系统就会报错。
-dontwarn android.support.v4.**
-dontwarn android.os.**

#编译时关掉Log的打印,
#-assumenosideeffects class android.util.Log { #如果是自定义Log类,修改下面的包名
# public static *** d(...);
# public static *** e(...);
# public static *** i(...);
# public static *** v(...);
# public static *** println(...);
# public static *** w(...);
# public static *** wtf(...);
#}

##############################--------以上是Android基本配置----------##############################

# 实体类不混淆(自己项目实体类不被混淆,因为Gson中用到了反射)
#-keep class me.febsky.entity.** { *; }
### -----这是个列子---- ###
#这个类里面的所有的成员变量和方法都不混淆
-keep class me.febsky.testprogard.MainActivity { *; }
#这个类里面的内部类的所有的成员变量和方法都不混淆
-keep class me.febsky.testprogard.MainActivity$* { *; }
#这个类里面的所有public的内部类的所有的成员变量和方法都不混淆
-keep public class me.febsky.testprogard.MainActivity$* { *; }
#这个类里面的内部类的类名不备混淆,方法和变量还是混淆的
-keep class me.febsky.testprogard.MainActivity$*
#保持一个类的类名不被混淆,里面的方法和成员变量名还是被混淆的
-keep public class me.febsky.testprogard.MyOutClass
### -----这是个列子---- ###

## for sharedSDK
-keep class android.net.http.SslError
-keep class android.webkit.**{*;}
-keep class cn.sharesdk.**{*;}
-keep class com.sina.**{*;}
-keep class m.framework.**{*;}


#butter knife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}

PS(PhotoShop)基础知识(二)

工具栏中的工具

  1. 移动工具 V [shift]

    • 在左上角有个自动选择 + 图层选项,勾选后可以自动选择获得焦点图层
    • 当有多个图层中的元素被选中的时候,还可以指定对其方式
  2. 选框工具 M

    • 在上边的样式选项中可以指定,选区工具是固定大小的还是固定比例的。
    • 选区可以做运算,也就是两次选区的组合。相加,相减,相交等。组合键,shift ,ALT ,shift + alt. 前提是已经存在一个选区,当画第二个选区的时候,按住这些键。
    • command + d 取消选区
    • command + j 复制选区为新的图层
    • 按住shift 选区可以为正方形,圆
    • 填充选区
      alt + shift + delete前景填充
      cmd + shift + delete 背景填充
  3. 套索工具 L

    • delete 键可以删除套索选择工具的点
  4. 快速选择工具

  1. 裁剪工具 C

    • 可以自定义放置裁剪变换中心点,按住alt 拖动中心点
    • 上边选项中可以选择辅助线,三等分,网格,黄金比,螺旋线等。shift + o 可以调整辅助线的位置。
    • 透视裁剪工具,可以通过自定义方式四个裁剪点,来。
    • 切片工具可以和参考线一起使用基于参考线的切片
    • 切片选择工具,可以移动切片
  1. 吸管工具

    • 吸到前景色
    • 按住alt + 鼠标左键 吸到背景色
    • 通过样本选项,可设置是吸取当前图层还是图层叠加
    • 通过取样大小可以设置是取一个像素点,还是周围几个像素点的像素评价运算
    • 颜色取样器工具信息窗口一块用
    • 标尺拉直工具 可以将倾斜的图层拉直

PS(PhotoShop)基础知识(三)

  1. 抓手工具 H

    • alt + 鼠标左键 + 左右拖动鼠标,放大或者缩小图片
    • alt + shift + 鼠标左键 + 左右拖动鼠标 放大或者缩小图片的当前位置点
    • command + +/- 放大或者缩小图片
    • command + 0/1 预览/完全显示图片
  2. 图层

    • 按住 alt 键和移动工具,可以边移动边复制图层
    • shift + command + alt + e 合并可见图层为一个新图层
    • 选中图层后,delete键可以删除图层
    • common + j 可以生成图层副本
    • 蒙版工具可以通过修改黑色的值,修改原图的透明度,为图层新建蒙版,alt 点击可以选中
    • shift 单击蒙版停用蒙版
    • 选区可以直接创建蒙版,蒙版黑色的部分表示透明,白色表示显示。可以用画笔工具在蒙版上涂抹
    • 有个智能对象
    • 图层样式 右下角FX按钮 双击右侧图层列表可以进入图层样式选项
    • 复杂图像不必抠图,用图像混合模式可以完成,去掉白色选择正片叠底

垂直方向的ViewPager

竖直方向的ViewPager

出处

效果图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

/**
* Uses a combination of a PageTransformer and swapping X & Y coordinates
* of touch events to create the illusion of a vertically scrolling ViewPager.
*
* Requires API 11+
*
*/
public class VerticalViewPager extends ViewPager {

public VerticalViewPager(Context context) {
super(context);
init();
}

public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
// The majority of the magic happens here
setPageTransformer(true, new VerticalPageTransformer());
// The easiest way to get rid of the overscroll drawing that happens on the left and right
setOverScrollMode(OVER_SCROLL_NEVER);
}

private class VerticalPageTransformer implements ViewPager.PageTransformer {

@Override
public void transformPage(View view, float position) {

if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);

} else if (position <= 1) { // [-1,1]
view.setAlpha(1);

// Counteract the default slide transition
view.setTranslationX(view.getWidth() * -position);

//set Y position to swipe in from top
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);

} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}

/**
* Swaps the X and Y coordinates of your touch event.
*/
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();

float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;

ev.setLocation(newX, newY);

return ev;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev); // return touch coordinates to original reference frame for any child views
return intercepted;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(swapXY(ev));
}

}

从别人app(Android)中拿数据

开篇之前,我一直考虑这算不算侵权?只是兴趣,如果有人告知侵权的话,立马删除。

我所说的数据不是指拿别人app中的图片资源。而是程序运行所需要的数据。

App中的数据无非两种,一种是网络数据,一种是本地数据。网络请求我们一般用抓包工具(Mac上的Charles 或者Windows上的Fiddler)来获取Api,然后在用程序循环请求,获取所有的数据。而另一种是app存储到本地的数据,一种是存储到文件中的,一种是存储到数据库中的。

快快查汉语词典

一次偶然的机会,发现快快查汉语词典这个app作为汉语词典来说,还算是比较良心的app,免费,而且界面还是比较干净的。而且词库比较全。所以我想获取这个词典的所有词库,作为自己的词库使用。像这种词典,一般汉字都用拼音散列开了。所以我想获取汉字和拼音的映射。好吧,用了一个周的时间,边学习,变反编译,并且学习了Smali语法。终于把数据解析出来了。

大体流程:数据解密—>数据格式化—>格式化后的数据

快快查汉语词典

作为一个词典来说,我感觉这个app做的相当不错。有很多值得我们学习的地方。so,我给反编译了。

  1. 首先确定数据来源。

    分析一下数据到底来源于网络呢,还是本地数据存储呢。这个很容易判断,第一次安装好app之后,断开网络,如果仍然能查询,说明本地有数据缓存,如果不能查询,说明必须要访问网络。好,测试之后我们发现能查询,说明本地可定有一份可供查询的数据存储

    有些app会做双重数据存储。也就是说数据源,既有网络也有本地,比如有道词典,会做一部分本地存储,详细信息要从网络查询。所以还要做,有网和无网情况下查询详情是不是一样,来判断本地是否存有所有的数据。经过比较发现本地确实有完整的数据存储

  2. 如果数据存储在本地,存在哪了?

    猜测: 本地数据

    理由:字--拼音--解释这种数据结构,一般不适合存放到文件中。

    实践:数据库存放无非三个位置,一个是/data/data/包名目录下,一个是/sdcard/Android/data/包名,最后一个就是SD卡了,随便建个文件夹就可以存储。参考,Android文件存放位置,那么如何判断数据库文件存放在这三个位置中的那个呢?我们来一个个排除:

    • 要访问真机的/data/data目录必须要root权限。当然我的测试机是roo了的。发现里面并没有任何有价值的数据库。
    • /sdcard/Android/data/包名这个目录是可以访问的,adb shell的方式访问之后也没有发现任何有价值的数据。
    • 那就剩下最后一个sd卡了。这个比较麻烦,因为这个路径不固定,可以任意创建文件夹。如果是测试机,那么sd卡上没有几个文件夹比价好排查。这里可以用一些小技巧。比如我们app的包名为com.kk.dict 所以按照一般程序员命名规则,这个文件夹如果真的在SD卡上的话,那么有很大的可能性和kk或者dict有关,哈哈,经过查找,在sd卡上有个叫kkdict的文件夹,最后发现所有的离线数据库都在这里面/sdcard/kkdict/dict/
    • 其实上面的三种排查方式比较大众化,也就是可以用于所有的情况下的排查,但是在分析别人的app的时候,我们的方法还是比较灵活的,不拘泥于这么几种,比如我们要分析的这个快快查词典。并不需要这么麻烦。这个app在我的-->设置-->功能包下载里面能够设置离线的数据库的存储路径。

词库下载位置

  • 由上图可以发现,在这之前的所有分析,都不用了,从离线路径可以分析出来,这数据查询就保存在本地,而且保存路径都可以看到了。

    结果: 和我们猜想的一样。

  1. 好那接下来我们就从数据库中拿数据了哦~

    可以看到/sdcard/kkdict/dict/目录下主要有以下数据库:
    快快查词典包含的主要数据库

    从名字结合app的界面可以大体分析出数据库中存储的内容。我们来看详解这个库,应该存储的是每个字的详细解释。用数据库查看软件查看一下我用的NavicateMac上没发现什么好用的sqlite3可视化查看软件。打开数据库,找到里面主要的表,这下懵逼了。表里的数据是用二进制Blob存储的。
    xiangjie.db/xiangjie表

    下面的主要任务就是把这个zhujie的字段表示的数据解析出来:how?这种分析主要从哪下手、我一般主要从两方面下手:直接分析数据库;从界面反编译代码后,从代码查询。显然后者难度很大,如果代码混淆了,读起来相当困难。我们先来实验第一种方式,看是否能够成功。也就是直接解析zhujie字段的二进制,翻译成字符串。

    猜测这个二进制是什么:有这么几种可能,对象,直接把数据所对应的对象存到数据库,以二进制的方式;字符串,把字符串转换成二进制数据写入数据库;加密,这也是最头疼的方式,如果真的加密了,必须去源码中找到加密算法和相应的解密密钥。

    试验:试着猜想一下,这种数据库中可能存放对象吗?我感觉以我的经验来说不大可能,这种存对象的方式,平台适用性太低。如果使用java存的对象,只能用java读出来,那么这个库完全无法再别的(如:IOS)平台上使用。这种不是不行只是不太理想。当时我猜测的最大可能是字符串的二进制格式,因为我以为这种数据没必要加密。所以,我就试着验证了一下。如何读数据库就不说了,参考

    从数据库以二进制的方式读入,然后用String解码。一运行,我靠,兄弟乱码??
    测试代码及运行结果

从上图看出,以我多年解决乱码的经验,出现乱码大多数是由于编码方式不正确引起的。吆西,编码那我改还不行吗?

于是有了下图的测试代码:
对二进制进行不同的字符编码

额。。。全是乱码,从查词的结果来看一定包含中文,我知道的能处理中文的常用编码也就这么三种,你说全是乱码。这。。。

结果:没办法猜想错误,0.0,只能继续猜测呗。

继续猜测: 会不会数据库存放的是Base64的二进制方式,作者会不会用Base64的方式对数据进行了简单的加密? 但是仔细一想,Base64主要作用是把二进制转换成字符来显示和表达的。把一个字符串,转换成二进制在转换成Base64再换成二进制,再存储。好像能有这种想法的人有点,那啥吧??既然没有别的办法也只能试试喽。测试代码就不给了,就是Base64的基本操作,就是流程绕了点,后来测试发现也是乱码。(:-

继续猜想:看了数据库的这个字段的数据真的加密了。只从数据库入手好像无法解决。加密有难有易,但是无论如何,他既然会显示到界面的数据是正常的明文,说明即使加密了,apk源码中也有解密代码。看了现在找到这个解密的代码是非常关键的。这个解密的代码也有难有易,如果做的简单点就是一个java的Util类。如果做的难了,用jni做到so文件中。但愿他在java中吧。这样反编译起来还简单些。

测试:要把apk中的dex拿出来反编译成jar,然后查看这个jar中的源文件。具体如何反编译拿到jar,不再给出,可以参考这个。参考 至于查看浏览这个jar包中的java代码,我主要借助两个工具jd-gui-1.3.0.jarjadx-0.6.0为什么要接着两个,我能力有限,查看反编译后的java代码,如果这个代码被混淆了,读起来相当困难,而这两个tool反编译后的java代码各有优缺点。前者有代码跟进功能,后者的代码反编译的可读性比较好,但是后者代码综合性比较强,比如能简写的他会简写。这样读起来也比价困难。结合来看就轻松多了。

好,我们可以看到最终数据会被现实到,汉字查询结果页面。如下:

汉字查询结果页面2

那么我们找到这个页面,然后就可以进行数据分析了,接下来的问题,这个页面怎么找??

猜测:这是个Activity,既然是详情页面,那么根据中国,程序员的英语水平命名的话,应该和Detail 和 Activity有关。好找一下。

jd-gui显示jar包文件

如图所示,可以看到,这个jar里面大多数是没有用的,都是些第三方的引入。被打包进来的。真正我们写的代码在如图箭头指向的包中。是主要的Activity。找来找去,还真找到了一个叫DetailActivity.class的类。哈哈…

打开一看彻底懵逼:全是a啊b啊的。这特么怎么看。没办法,反编译比人的工程代码,需要极大的耐心。慢慢来看,有些小技巧在里面,我们并不需要全看,要想理解大体思路,也需要一定的代码基础。至少自己独立架构过一个项目。或者参与过很多项目。

思路:接下来要做的就是找到显示着段文字的View—>找到这个View的赋值过程—>找到这个值得来源—>这个来源中一定包含解密过程这个思路想起了简单,但是操作起来想当复制,我们可以看到这个DetailActivity.class类大约有几千行代码啊。如何找?

猜测:据我所知,在Android常用的显示文本的控件也就那么三个TextView,EditText,WebView。到底是哪个,我们要借助于一个工具叫Android Device Monitor 这是分析比人程序布局的利器啊。如何使用?这个地方先空一下,改天补上 因为用起来不难,但是描述起来很烦人。来看分析截图:

字典详情页面分析截图

从红色箭头标出的部分看,6不6,这界面用了什么View,View的层级关系,View的Id都能得到。拿到了这些再去DetailActivity.class中分析是不是就简单多了。可以确定这个就是为WebView赋值而已嘛。找到WebView的赋值的地方,也就找到了数据源了。这时候就兴奋的去DetailActivity.class中找WevView了,我去,没有???why?

猜测:页面用了Fragment,数据在Fragment中,界面用了组合View,WebView在组合View中。分析到这里似乎难以用一种固定的方式分析了,好像要凭感觉?怎么办,转向layout 的xml文件,找点突破口。这时候就用到jadx出厂了,为啥?自己对比。
jadx

从图片中可以看到,我们很容易拿到了,这个activity的布局文件。打开它。

detail.xml

分析后发现果然,主要的数据都放到了ViewPager中。好啊,接下来的主要工作就转战这个ViewPager,在这个DetailActivity中可定有这个Viewpager的引用。继续用jadx。搜索这个ViewPager的id找到,他的引用。如下图。

查找ViewPager的引用

好嘛,兄弟,你在Activity中的引用叫this.B啊,让我这个好找啊。既然找到了ViewPager,我们想要ViewPager中的每一个Pager的数据,怎么找,找啥?找Adapter不论是啥,一定会有setAdapter这个方法。继续搜索。this.B.setAdapter

查找Adapter的设置方法。

一切如上图。其实感觉好的可以看到我们的方框框起来的就是我们最终要找的。好这个PagerAdapter的名字竟然叫j。Adapter有两种方式存在,一种是单独的文件,另一种是匿类的方式,这就是我们的开发常识了。先猜测就在本文件中吧。搜索Adapter.如下图:

Adapter搜索。

从上图中我们可以分析出ViewPager的View集合了,this.a.C认真读下代码,这个this指的是j也即是PagerAdapter的实例对象。而a,指的是DetailActivity.this,好了,集合对象找到了就是DetailActivity中的C,搜索this.C,结果如下图:

搜索this.C ViewPager的Pager页

图片中包含唯一的自定义View就是DetailContentView,打开它看看。哈,果然WebView就在这里面。如下图:

DetailContentView

这货对外暴露的设置内容的方法为a(string),好。在DetailActivity中可以看到:

1
2
3
4
5
6
this.U = (DetailContentView) inflate.findViewById(R.id.detail_zhujie_id);
this.V = (DetailContentView) inflate.findViewById(R.id.detail_xiangjie);
this.W = (DetailContentView) inflate.findViewById(R.id.detail_guhanyu);
this.X = (DetailContentView) inflate.findViewById(R.id.detail_kangxi);
this.Y = (DetailContentView) inflate.findViewById(R.id.detail_shuowen);
this.Z = (DetailContentView) inflate.findViewById(R.id.detail_wys_id);

我们从这里面随便拿一个分析就行。我就选this.W吧。这个对应详情页的古汉语展开的详情。

那么这个this.W要想设置数据可定调用了a方法。搜索this.W 大小写敏感。注意自己过滤一些无关的搜索。结果可以看到唯一符合条件的是:

搜索赋值方法

可以看到,这个p.c(this,this.aX.c)+this.aX.c)就是获取数据源的方法。猜测,这个p.c方法可能是某个帮助类。对数据进行处理,也可能是某个查询类根据传入的参数来查询,到底是什么呢?我们用jd-gui来帮助我们跟进代码。来跟进参数this.aX.c打这个东西。(P.S.这个p.c也是相当有用的。剧透一下用于字符串的格式化)如下图:

数据库查询类

可以看到 aX就是上图中a这个类的对象,而c就是这个对象的成员,在结合上面的拼音,我们断定,这个a类是和数据库表结构对应的Bean类。进而我断定这个a的外部类就是数据库表的查询类。查询方法在上面。数据库表结构如下图:

古汉语的表结构

从上上个图中,我们从a类的定义往上看,猜我看到了啥。哈哈,getBlob方法,很多时候感觉还是很重要的。一种熟悉的感觉有木有。一开始的时候我们就是用他读的数据的那个二进制字段。从这行代码往下读,看不懂?我也看不懂。但是我读出来,这个a类中的c字段就是我们要的东西。继续看这个c是怎么获取来的?

1
locala.c = q.a(paramList,i)    //paramlist 就是我们的二进制byte数组,是数组的长度,哈哈,这个方法就是解密方法喽。

我们要做的就是找到,这个方法,把他提取出来,就能解密数据所有的二进制了。这是个静态方法,是比较独立的工具类。所以提取没有什么难度?但是也遇到很多问题,关键是有些加密代码看不懂。


万里长征走一半了?No,才刚刚开始。还记得我们要干嘛嘛?要拿到解密后数据的数据。继续吧:

jd-gui跟进代码。q.a(byte[],int)。反编译查找方法时候一定注意方法签名。有可能都是特么的a 但都是重载,别找错了。这个方法传递一个加密后的二进制数据和他的长度进去,返回一个解密后的字符串。

我发现不能再往下写了,再写人家的加密算法就给拿出来了。

来看这个方法的签名:
解密方法签名
P.S.看反编译后的代码要学会自动过滤无关代码

这个方法有用的即使三行。而这三行中唯一不知道的就是d也就是d.getBytes()中的d,继续分析这个类看看这个d是如何获取的。a(byte[],int)是个静态方法,所以d不可能在构造方法中初始化,一定在这个方法内初始化的,为啥找不到???对比jadx,效果如下图:

jadx反编译a方法

从上图可以看到,jadx的反编译结果,比jd-gui多了一个调用a()a的空方法,这才符合我的猜想嘛。接下来看看这个a()的定义,只要能拿到d的值。这个解密过程就算完成了。哈哈,这个不能贴了,这是人家的密钥生成方式。不过这个方法我是真心看不懂,不过没关系,这个方法没有引用其他的东西,直接拷出来,引入生成一个d就行了。至此解密完成。如下图所示:

解密后的数据

这不太对啊。这数据格式太丑了,还包含什么乱七八糟的字符?要处理这些就是前面所说的字符串格式化。加入css样式和加入html标签。这个过程更是相当复杂。各种猜测和尝试,其中还借助了Smali文件,重新打包,打印log等。不过还好,最终成功了,我给封装成ExplainUtil,哈哈他的app中也是这么命名的,不过混淆过后就成了a,b,c。。。这种乱七八糟的东西了。

这篇文章写了五个小时~~~

http://febsky.me

NodeJs包管理npm

  1. node 版本更新 n 的使用

    node有一个模块叫 n,是专门用来管理node.js的版本的。

    $ npm install -g n 安装n模块:

    $ sudo n stable 升级node.js到最新稳定版

    $ sudo n 5.8.0 升级node.js到指定版本号

    输入n后输出当前已经安装的node版本以及正在使用的版本(前面有一个o),你可以通过移动上下方向键来选择要使用的版本,最后按回车生效。

    1
    2
    3
    $ n
    6.0.0
    o 5.8.0
  2. npm的常用命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    npm -v          #显示版本,检查npm 是否正确安装。
    npm install express #安装express模块
    npm install -g express #全局安装express模块
    npm list #列出已安装模块
    npm show express #显示模块详情
    npm update #升级当前目录下的项目的所有模块
    npm update express #升级当前目录下的项目的指定模块
    npm update -g express #升级全局安装的express模块
    npm uninstall express #删除指定的模块
    npm update –g 更新你已经安装的NPM库
  3. n 的基本操作

    1
    2
    3
    4
    5
    6
    7
    8
     安装
    $ n 8.0.0
    安装最新版本
    $ n latest
    安装稳定版本
    $ n stable
    删除某个版本
    $ n rm 8.0.0

Android中反编译Smali文件解读

  1. 寄存器

    修改Smali时有一件很重要的事情就是要注意寄存器。如果乱用寄存器的话可能会导致程序崩溃。每个方法开头声明了registers的数量,这个数量是参数和本地变量总和。参数统一用P表示。如果是非静态方法p0代表this,p1-pN代表各个参数。如果是静态方法的话,p0-pN代表各个参数。本地变量统一用v表示。如果想要增加的新的本地变量,需要在方法开头的registers数量上增加相应的数值。

    比如下面这个方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     # direct methods
    .method public constructor <init>()V
    .registers 1

    .prologue
    .line 7
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
    .end method

    因为这不是静态方法,所以p0代表this。如果想要增加一个新的本地变量,比如v0。就需要把.registers 1改为.registers 2。

    有两种方式指定一个方法中有多少寄存器是可用的

    1. .registers 使用这个指令指定方法中寄存器的总数
    1. .locals 使用这个指定表明方法中非参寄存器的总数,放在方法的第一行。

    https://github.com/JesusFreke/smali/wiki/Registers

    寄存器的命名方式

    有两种方式——V命名方式和P命名方式。P命名方式中的第一个寄存器就是方法中的第一个参数寄存器。在下表中我们用这两种命名方式来表示上一个例子中有5个寄存器和3个参数的方法。

    1
    2
    3
    4
    5
    v0    第一个local register  
    v1 第二个local register
    v2 p0 第一个parameter register
    v3 p1 第二个parameter register
    v4 p2 第三个parameter register

    你可以用任何一种方式来引用参数寄存器——他们没有任何差别。
    注意:baksmali(apktool)默认对参数寄存器使用P命名方式。如果想使用V命名方式,可以使用-pl—no-parameter-registers选项。但是有的工具反编译的出来的都是v命名的方式。比如(smaliviewer),使用P命名的方式可读性比较好。

    使用P命名方式是为了防止以后如果要在方法中增加寄存器,需要对参数寄存器重新进行编号的缺点。

  1. 给原程序增加大量逻辑的办法

    我非常不建议在程序原有的方法上增加大量逻辑,这样可能会出现很多寄存器方面的错误导致编译失败。比较好的方法是:把想要增加的逻辑先用java写成一个apk,然后把这个apk反编译成smali文件,随后把反编译后的这部分逻辑的smali文件插入到目标程序的smali文件夹中,然后再在原来的方法上采用invoke的方式调用新加入的逻辑。这样的话不管加入再多的逻辑,也只是修改了原程序的几行代码而已。这个思路也是很多重打包病毒惯用的伎俩,确实非常方便好用。

  2. 方法中出现的关键字

    1
    2
    3
    4
    5
    6
    7
     .method <访问权限>[修饰关键字]<方法原型>
    <.locals> # 指定了使用的局部变量个数
    [.parameter] # 指定了方法的参数,如果有三个参数就有三个.parameter
    [.prologue] # 指定了代码开始段,混淆过的代码可能去掉了改段落
    [.line] # 指定了该处指令在源代码中的行数,混淆过的代码可能会去掉,主要用于调试。
    <代码体>
    .end method
    1
    2
    3
    4
    5
    6
    7
    8
    invoke-super  调用父函数
    const/high16 v0, 0x7fo3  把0x7fo3赋值给v0
    invoke-direct  调用函数
    return-void  函数返回void
    new-instance  创建实例
    iput-object  对象赋值
    iget-object  调用对象
    invoke-static  调用静态函数

【参考文章】

Smali 语法:

http://www.cnblogs.com/lee0oo0/p/3728271.html

http://dalufan.com/2015/01/14/android-Smail-learn/

官网文档:
转载官网Android官网,科学上网

Dalvik字节码