Android 8.0 应用保活实践

虽然我也觉得强行保活应用挺不厚道的,但是没办法,为了完成需求。

一开始尝试的方案是 Android 5.0 后系统提供的 JobScheduler,能够预先设置条件,达到条件时自动启动 JobService,在 Android 8.0 以下都能很愉快的使用。但是在华为的 Android 8.0 上,当应用被杀后,JobService 就不能被系统调用了。

于是采取了双进程服务绑定方式,实现了应用保活功能。

直接看原理图:

aidl_keep_alive

原理就是利用 Binder 的讣告机制,如果 Service Binder 实体的进程被杀,系统会向 Client 发送讣告,这个时机就是保活的空子了。所以可以通过两个进程启动两个 Binder 服务,互为 C/S,一旦一个进程挂掉,另一个进程就会收到 Binder 讣告,这时可以拉起另一个进程。

那么图中两个进程中的 TransferActivity 是干什么用的 ,这个在后面再说。

这里我写了两个应用,一个是 AIDLServer,相当于服务端;一个是 AIDLClient,相当于客户端。而两个进程之间的通信采用 AIDL 方式。

1
2
3
4
5
6
7
8
9
10
11
12
// IMyAidlInterface.aidl
package com.wuzy.aidlserver;

// Declare any non-default types here with import statements

interface IMyAidlInterface {

void bindSuccess();

void unbind();

}

注意两个应用的 AIDL 文件必须一致,包括包名。

然后,编写两个 binder 实体服务 RemoteService 、LocalService,主要代码如下:

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
public class RemoteService extends Service {

private static final String TAG = "RemoteService";

@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: 创建 RemoteService");
bindLocalService();
}

@Override
public IBinder onBind(Intent intent) {
return stub;
}

private IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public void bindSuccess() throws RemoteException {
Log.e(TAG, "bindSuccess: LocalService 绑定 RemoteService 成功");
}

@Override
public void unbind() throws RemoteException {
Log.e(TAG, "unbind: 此处解除 RemoteService 与 LocalService 的绑定");
getApplicationContext().unbindService(connection);
}
};

/**
* 绑定 LocalService
*/
private void bindLocalService() {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.wuzy.aidlclient", "com.wuzy.aidlclient.LocalService"));
if (!getApplicationContext().bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
Log.e(TAG, "bindLocalService: 绑定 LocalService 失败");
stopSelf();
}
}

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {

}

@Override
public void onServiceDisconnected(ComponentName name) {
// bindRemoteService();
createTransferActivity();
}
};

private void createTransferActivity() {
Intent intent = new Intent(this, TransferActivity.class);
intent.setAction(TransferActivity.ACTION_FROM_SELF);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}

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
public class LocalService extends Service {

private static final String TAG = "LocalService";

@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: 创建 LocalService");
bindRemoteService();

}

@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind: 绑定 LocalService");
return stub;
}

private IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public void bindSuccess() throws RemoteException {
Log.e(TAG, "bindSuccess: RemoteService 绑定 LocalService 成功");
}

@Override
public void unbind() throws RemoteException {
getApplicationContext().unbindService(connection);
}
};

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {

}

@Override
public void onServiceDisconnected(ComponentName name) {
// bindRemoteService();
createTransferActivity();
}
};

private void createTransferActivity() {
Intent intent = new Intent(this, TransferActivity.class);
intent.setAction(TransferActivity.ACTION_FROM_SELF);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

private void bindRemoteService() {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.wuzy.aidlserver", "com.wuzy.aidlserver.RemoteService"));
if (!getApplicationContext().bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
Log.e(TAG, "bindRemoteService: 绑定 RemoteService 失败");
stopSelf();
}
}
}

在 onCreate 的时候相互绑定,并在 onServiceDisconnected 收到讣告的时候,重新启动服务绑定彼此即可。

但是我在系统是 8.0 的华为机器上是无效的,也就是当 LocalService 所在进程被杀后,RemoteService 无法启动LocalService,反过来也是如此。

所以,这里只能采取 “曲线救国” 的方式。通过 TransferActivity 中转下,先启动守护进程的 TransferActivity,再从守护进程的 TransferActivity 中启动保活进程的 TransferActivity,这是没有问题的,再从保活进程的 TransferActivity 中启动 LocalService,重新绑定服务即可,反过来也是一样的。当然,TransferActivity 要用户无感知,不然会很突兀,所以这里的 TransferActivity 都是 1 个像素,做完任务及时销毁即可。

TransferActivity 的代码就不贴了,具体可以去 GitHub 了解。

这种方式用来保活一般是没有问题的,因为 Binder 讣告是系统中 Binder 框架自带的,除非一次性杀了两个进程,那就没辙了。

最后,一般保活的目的是为了做某项任务,所以,任务完成后应该结束保活功能,不然老是占着内存确实挺不厚道的。

Android 音视频基础知识

视频编码

通过特定的压缩技术,将某个视频格式文件转换成另一种视频格式文件的方式。编解码标准有国际电联的 H.261、H.263、H.264,运动静止图像专家组的 M-JPEG 和国际标准化组织运动图像专家组的 MPEG 系列标准,此外,互联网上还有 Real-Networks 的 RealVideo,微软公司的 WMV 以及 Apple 公司的 QuickTime 等。

  • MPEG:主要有 MPEG1(VCD),MPEG2(DVD)、MPEG4(DivX、XviD)、MPEG4 AVC。
  • H.26X :侧重网络传输,包括 H.261、H.263、H.263、H.263+、H.263++、H.264。

音频编码

  • MP3:将声音以 1: 10 甚至 1:12 的压缩率进行压缩。对高频信号使用大压缩率,低频信号使用小压缩率,保证信号不失真。
  • AAC:与 MP3 相比,优点是音质更佳、文件更小。缺点是属于有损压缩,与时下流行的 APE、FLAC 等无损压缩格式相比音质差距较大。
  • AC3:Dobly 实验室所发展的有损音频编码方式,广泛应用于激光唱片和 DVD。AC3提供的环绕声系统由五个全频域声道(左前、中央、右前、左后、右后)和一个超低音声道,广泛用于电影院。

相关知识点

1、帧率

测量显示帧数的量度。fps:每秒显示帧数。

2、

Android 单元测试 Junit

示例代码:https://github.com/zywudev/AndroidUnitTest

Anroid Studio 在新建项目中自动将 Junit 框架集成,无需额外导入依赖。

Junit Assert

Assert 就是断言,判断假设与实际是否一致,一致则测试通过。

常用断言:

  • assertTrue 假设为真
  • assertFalse 假设为假
  • assertEquals 假设相同(基本数据类型或者对象)
  • assertNotEquals 假设不相同(基本数据类型或者对象)
  • assertNull 假设为空
  • assertNotNull 假设不为空
  • assertSame 假设相同(只能是对象)
  • assertNotSame 假设不相同(只能是对象)
  • assertArrayEquals 假设数组相同
  • assertThat 断言实际值是否满足指定的条件

期望值是前一个参数,实际值是后一个参数。

assertThat

1
2
3
assertThat(T actual, Matcher<? super T> matcher);

assertThat(String reason, T actual, Matcher<? super T> matcher);

其中,reason 为断言失败的输出信息,actual 为实际值,matcher 为匹配器。

常用的匹配器整理:

  • is 断言参数等于后面给出的匹配表达式
  • not 断言参数不等于后面给出的匹配表达式
  • equalTo 断言参数相等
  • equalToIgnoringCase 断言字符串相等忽略大小写
  • containsString 断言字符串包含某字符串
  • startsWith 断言字符串以某字符串开始
  • endsWith 断言字符串以某字符串结束
  • nullValue 断言参数的值为null
  • notNullValue 断言参数的值不为null
  • greaterThan 断言参数大于
  • lessThan 断言参数小于
  • greaterThanOrEqualTo 断言参数大于等于
  • lessThanOrEqualTo 断言参数小于等于
  • closeTo 断言浮点型数在某一范围内
  • allOf 断言符合所有条件,相当于&&
  • anyOf 断言符合某一条件,相当于或
  • hasKey 断言Map集合含有此键
  • hasValue 断言Map集合含有此值
  • hasItem 断言迭代对象含有此元素

自定义匹配器:

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
/**
* Created by wuzy on 2018/10/26.
* 自定义匹配器,判断数字是否在范围内
*/

public class IsNumberRangeMatcher extends BaseMatcher<Integer> {

private int start;

private int end;

public IsNumberRangeMatcher(int start, int end) {
this.start = start;
this.end = end;
}

@Override
public boolean matches(Object item) {
if (item == null) {
return false;
}
Integer i = (Integer) item;
return start <= i && i <= end;
}

@Override
public void describeTo(Description description) {

}
}

http://www.vogella.com/tutorials/Hamcrest/article.html

Junit Annotation

常用注解:

  • @Test 表示此方法为测试方法
    @Before 在每个测试方法前执行,可做初始化操作
    @After    在每个测试方法后执行,可做释放资源操作
    @Ignore 忽略的测试方法
    @BeforeClass 在类中所有方法前运行。此注解修饰的方法必须是static void
    @AfterClass 在类中最后运行。此注解修饰的方法必须是static void
    @RunWith 指定该测试类使用某个运行器
    @Parameters 指定测试类的测试数据集合
    @Rule 重新制定测试类中方法的行为
    @FixMethodOrder 指定测试类中方法的执行顺序

执行顺序:

@BeforeClass –> @Before –> @Test –> @After –> @AfterClass

@Test

它可以接受两个参数,一个是预期异常,一个是超时时间。

即不出现预期异常则测试不通过;超过超时时间则测试不通过。

1
2
3
4
5
6
7
8
9
@Test(expected = ParseException.class)   // 日期解析错误,预期异常
public void dateToStamp1() throws Exception {
DateUtil.dateToStamp(time);
}

@Test(timeout = 100)
public void dateToStamp2() throws Exception {
DateUtil.dateToStamp(time);
}

@Parameters

参数化测试,用于测试数据集合。

1
2
3
4
5
6
7
8
9
@RunWith(Parameterized.class)
public class DateUtilTest {

@Parameterized.Parameters
public static Collection primeNumber() {
return Arrays.asList("2018-10-24", "2018-10-24 16:18:28","2018年10月24日 16点18分28秒");
}
...
}

@Rule

Junit 提供自定义规则。

  • 实现 TestRule 接口,实现 apply 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Created by wuzy on 2018/10/26.
* 自定义 Rule,单元测试方法执行前后打印
*/
public class MyRule implements TestRule {

@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
// evaluate前执行方法相当于@Before
String methodName = description.getMethodName(); // 获取测试方法的名字
System.out.println(methodName + "测试开始!");

base.evaluate(); // 运行的测试方法

// evaluate后执行方法相当于@After
System.out.println(methodName + "测试结束!");
}
};
}
}
  • 在测试类中添加自定义 Rule 。
1
2
3
 // 添加自定义 Rule
@Rule
public MyRule myRule = new MyRule();

经济机器是怎样运行的?

最近看到的一个视频,是大师级人物瑞达利欧自己的宏观经济框架,三十分钟解释了经济机器是怎样运行的,通俗易懂。

视频地址:https://www.youtube.com/watch?v=rFV7wdEX-Mo

以下是个人总结的要点,结合视频看,效果更佳。

经济是由几个简单的零部件和无数次重复的简单交易组成,这些交易首先由人的天性所驱动,因而形成了三股主要的经济动力。

1、生产率的提高

2、短期债务周期

3、长期债务周期

把三股动力结合在起来,就是一个简单有效的宏观经济分析模型。

image_1

交易

交易时刻在发生,你每次买东西都是进行一笔交易。

image_2

在一次交易中,买方可以使用「货币、信用」向卖方交换「商品、服务、金融资产」。

信用在使用时和货币是一样的,经济社会中同时存在着货币和信用,把两者相加,就是支出总额—这是经济的驱动力。

支出之所以是经济的驱动力,是因为一个人的支出是另一个人的收入。

在经济体中,有四种交易主体:个人、企业、银行和政府。

image_3

其中政府是最大的买方和卖方。政府包括中央政府和中央银行。

中央银行控制经济中的货币和信贷数量,主要通过利率和发行货币来控制。

image_4

信贷

人们在交易过程中除了使用货币还会使用「信用」。

一旦使用信用,则会同时产生「信贷」和「债务」,债务是贷款人的资产,是借款人的负债,借款人一旦获得信贷,便可以增加自己的支出,而一个人的支出是另一个人的收入,这就导致借贷在不断增加循环。这一驱动模式导致经济增长,也就形成了长短债务周期。

image_5

经济周期

生产率的提高是经济发展的根本动力,如果抛开其它动力的影响,生产率提高会使得经济长期缓慢线性发展。

但信贷是导致经济周期的根本原因。为什么呢?

因为债务本质上是向未来的自己借钱,提前消费。你给自己设定了一个未来的时间,到那个时候你的支出必须少于收入,以便偿还债务,这样马上就产生了一个短期债务周期。

image_6

短期债务周期

如果支出和收入的增长速度超过所售商品的生产速度,价格就会上涨,我们把价格的上涨称为通货膨胀。

央行会通过利率调节债务成本,从而把短期负债控制在合理范围内。

通货膨胀-> 价格上涨 -> 提高利率-> 借贷人减少、现有债务成本上升-> 支出资金减少-> 收入下降-> 商品价格下跌-> 经济衰退 -> 降低利率 -> 经济增长

短期债务周期通常持续5-8年,在几十年里不断重复。

但是请注意在每个周期的低谷和高峰后,经济增长和债务都超过前一个周期。为什么会这样,这是人促成的,人具有借更多钱和花更多钱的倾向,而不喜欢偿还债务。这是人的天性,因此在长期内债务增加的速度超过收入,从而形成长期债务周期。

长期债务周期

长期债务周期通常持续大约75-100年。

当债务负担过大,亦或者短期债务周期使得某个时间段内收入过少,长期债务的压力可能就会快速释放,从而形成经济危机,政府随之开始去杠杆。

去杠杆化

image_7

主要通过四种办法:

1、个人、企业和政府削减支出

2、通过债务违约和重组来减少债务

3、财富再分配将从富人转给穷人

4、最后央行发行更多货币

削减支出

削减支出会导致支出减少,而一个人的支出是另一个人收入,这就导致收入减少。收入下降速度超过还债速度,债务负担会更为严重。企业不得不削减成本,意味着工作机会减少,失业率上升。

债务重组

如果借款人不偿还债务,出现违约,存款人纷纷取出存款,这种严重的经济收缩就是萧条。

债务重组让债务消失,但它导致收入和资产价值消失,和削减支出一样会导致通货紧缩。这些会对中央政府产生影响,因为收入降低和就业减少意味着税收减少,失业率上升,政府需要增加支出。如果政府的支出超过税收,就会导致财政赤字。

财务再分配

由于政府需要更多的钱,而大量财富集中在少数人手中,政府自然而然地增加对富人的征税,将财富从富人那里转给穷人。但是这会导致穷人富人相互怨恨,容易造成社会动荡。

发行货币

当利率接近零的水平,央行就不得不发行更多货币,发行货币与前三种方式不同,会引起通货膨胀和刺激经济。

央行一方面通过货币购买金融资产,帮助推升资产价格从而提高人们的信用,但这仅仅有助于那些有金融资产的人。另一方面,央行通过购买政府债券,其实就是把钱借给政府使其能够运行赤字预算,并通过刺激计划和失业救济金来增加购买商品和服务的支出,这就增加了人们的收入。

和谐的去杠杆化

如果决策层取得适当的平衡,推动收入比债务增长得快,债务负担下降,这就是和谐的去杠杆化。

三条经验法则

1、不要让债务的增长速度超过收入。因为债务负担最终将把你压垮。

2、不要让收入的增长速度超过生产率。因为这最终将使你失去竞争力。

3、尽一切努力提高生产率。因为生产率在长期内起着最关键的作用。

Android View 的绘制过程

View 整体结构

Activity、Window、DecorView 之间的关系:

view_1

Activity: 类似控制器,统筹视图的添加与显示,以及通过回调来与 Window、View 进行交互。

Window:视图承载器,抽象类,PhoneWindow 是其唯一实现类。

DecorView:顶级 View,包含 StatusBar、TitleBar + ContentView 和 NavigationBar。

StatusBar 是状态栏;

TitleBar 对应各种 ActionBar;

ContentView 对应 R.id.content,setContentView 设置的 View 被添加到 R.id.content 对应的 View 上。

NavigationBar是虚拟按键。

ViewRoot: 实现类是 ViewRootImpl,它是连接 WidowManager 和 DecorView 的纽带。View 的三大流程(测量(measure),布局(layout),绘制(draw))均通过 ViewRoot 来完成。ViewRoot 继承了 Handler 类,可以接收事件并分发,Android 的所有触屏事件、按键事件、界面刷新等事件都是通过 ViewRoot 进行分发的。

View 的工作流程

view_2

View 的绘制是从上往下一层层迭代,DecorView –> ViewGroup(—>ViewGroup)–> View ,依次 measure、layout 、draw。

measure

ViewGroup.LayoutParams

布局参数类,指定视图 View 的高度和宽度等参数。

MeasureSpec

测量规格类,决定一个视图的大小。

measureSpec:32 位 int 值,高 2 位代表 mode,低 30 位代表 size。

mode:测量模式,包括:

  • UNSPECIFIED:父 View 不约束当前 View 的大小。
  • EXACTLY:对应 LayoutParams 中的 match_parent 和具体数值这两种模式。父 View 决定当前 View 的精确大小。
  • AT_MOST:对应 LayoutParams 中的 wrap_content,View 的大小不能大于父容器的大小。

size:某种测量模式下的规格大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;

public static int makeMeasureSpec(int size, int mode) {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}

public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}

measure 流程

1、ViewRootImpl.performMeasure -> performMeasure()

根据手机屏幕的宽高和 DecorView 的 LayoutParams 生成 DecorView 的 MeasureSpec,然后调用 DecorView 的 measure() 开始 DecorView 的测量。

2、DecorView.measure() -> onMeasure()

DecorView 继承自 FrameLayout,所以会走到 FrameLayout 的 onMeasure() 方法,onMeasure() 里面会调用 measureChild() 为 ViewGroup 生成 MeasureSpec,通过 ViewGroup 的 measure() 开始 ViewGroup 的测量。

3、ViewGroup.measure() -> onMeasure()

如果自定义的 ViewGroup 没有重写 onMeasure() 方法,默认会调用 View.onMeasure() 方法,则不会继续对子 view 进行测量。

因此,自定义 ViewGroup,需要重写 onMeasure() 方法,里面调用 measureChild() 为子 View 生成 MeasureSpec 并测量 child。最后调用 setMeasuredDimension 来设置自己的宽高。

4、View.measure() -> onMeasure()

根据父 View 的 MeasureSpec 和自身的 LayoutParams 参数进行测量。

view_3

细节:

父 View 的测量方法

根据子 View 的布局样式,调用 setMeasuredDimension 来设置自己的宽高。

子 View 的测量方法

根据父 View 的 MeasureSpec 和 自身的 LayoutParams 参数进行测量。

先计算子 View 的MeasureSpec,即 childMeasureSpec;

再调用 child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 测量子 View 的大小。

1
2
3
4
5
6
7
8
9
10
11
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

childMeasureSpec 的计算:

由 parentMeasureSpec 和 childDimension 确定。childDimension 为 LayoutParams 的 width 和 height。

规律

  • 当子 View 采用具体数值
    • mode: EXACTLY
    • size: 自身设置的具体数值
  • 当子 View 采用 match_parent
    • mode: 父容器的测量模式
    • size: 若父容器为EXACTLY,则大小为父容器的剩余大小;若父容器为 AT_MOST,则大小为不超过父容器的剩余大小。
  • 当子 View采用 wrap_content
    • mode:AT_MOST
    • size:不超过父容器的剩余空间

layout

计算视图的位置,也就是 left、top、right、bottom。这些坐标都是相对于父布局的坐标。

布局也是自上而下,不同的是 ViewGroup 先在 layout() 中确定自己的布局,然后在 onLayout() 方法中再调用子View 的 layout() 方法,让子 View 布局。

view_4

draw

draw 主要流程:

  • 绘制背景 background.draw(canvas)
  • 绘制自己(onDraw)
  • 绘制Children(dispatchDraw)
  • 绘制装饰(onDrawScrollBars)

view_5

参考

Window、Activity、DecorView以及ViewRoot之间的关系

View测量、布局及绘制原理

Android Clean 架构

Clean 一般是指,代码以洋葱的形状依据一定的依赖规则被划分为多层:内层对于外层一无所知。这就意味着依赖只能由外向内

clean_1

  • 架构独立。架构不依赖于一些满载功能的软件库。
  • 可测试性。业务规则可以在没有 UI、数据库等外部元素的情况下完成测试。
  • UI的独立性。在不改变系统其余部分的情况下完成UI的简易修改。
  • 数据库的独立性。业务规则不绑定数据库,可以随意更换数据库的实现。
  • 外部机制独立。业务规则不知道外层的事情。

Framework and Drivers

框架或者驱动,包括 UI、框架、数据库实现、网络实现细节等。

Interface Adapter

接口适配层,负责将实现细节和业务逻辑连接起来的粘合层。

Business rules(Interactors)

业务规则,整合了实现系统需要的所有实例。

Domain logic

封装了业务实体。实体可以是包含有方法的对象,或者一系列的数据结构、函数。

依据这些规则将工程分为三层:

clean_2

Presentation Layer

MVC 或者 MVP 对应的地方,不处理 UI 以外的任何逻辑。

Domain Layer

业务逻辑 Use Case 实现的地方。属于系统最内层。

这一层为纯 Java 代码,不牵扯任何 Android 相关依赖,规定了要做什么,具体实现细节交给外层。

Data Layer

clean_3

所有系统需要的数据通过这一层的 Repository 获取, 这是一种 Repository 模式,具体看这里。Repository 接口定义是在 Domain 层,接口表示怎么去存储或者访问数据,这些是业务逻辑,但是具体的实现与业务逻辑无关,应该交给 Data 层。

总结

1、Clean 架构中内层意味着抽象,外层意味着细节,同样一个抽象可能有多个子类,这种一对多的方式更具灵活性。

2、细节依赖抽象,业务逻辑制定规则,外层实现接口,这样能保证在内层能够调用外层组件去实现需要的逻辑,这里依据的是 DIP。

3、Clean 架构较为繁琐,如果是简单项目,完全没必要使用。

测试方法

  • Presentation Layer: 使用 AndroidInstruction 和 espresso 做集成测试和功能测试
  • Domain Layer:使用 JUnit 和 mockito 做单元测试
  • Data Layer:使用 Robolectric(这层有Android依赖)和 junit、mockito 做集成和单元测试。

学习项目

Android 事件分发机制

MotionEvent

根据面向对象思想,事件被封装成 MotionEvent 对象,以下是几个与手指触摸相关的常见事件:

  • ACTION_DOWN : 手指初次触摸到屏幕时触发。
  • ACTION_MOVE:手指在屏幕上滑动时触发,会触发多次。
  • ACTION_UP:手指离开屏幕时触发。
  • ACTION_CANCEL:事件被上层拦截时触发。

对于单指操作,一次触摸事件流程是这样的:

按下(ACTION_DOWN)–> 滑动(ACTION_MOVE)–> 离开(ACTION_UP)。如果只是简单的点击,则没有 ACTION_MOVE 事件产生。

事件分发、拦截与消费

与事件分发相关的三个重要方法:

  • dispatchTouchEvent:事件分发机制中的核心,所有的事件调度都归它管。
  • onInterceptTouchEvent:事件拦截。
  • onTouchEvent:事件消费处理。

事件分发流程

事件分发流程示意图:

android_touch

大致解释一下:

  • 图中 ViewGroup 与 View 之间省略了若干层 ViewGroup。

  • 触摸事件都是先交由 Activity 的 dispatchTouchEvent 方法(在此之间还有一系列的操作,在此省略了),再一层层往下分发。当中间的 ViewGroup 不进行拦截时,事件会分发给最底层的 View,由 View 的 onTouchEvent 方法进行处理,如果事件一直未被处理,最后会返回到 Activity 的 onTouchEvent

  • 图中 View/ViewGroup 的 onTouchEvent 返回 false,并不是直接调用上层的 onTouchEvent 方法。而是上层的 dispatchTouchEvent 方法接收到下层的 false 返回值时,再将事件分发给自己的 onTouchEvent 处理。

  • onInterceptTouchEvent 只存在于 ViewGroup 中。ViewGroup 是根据 onInterceptTouchEvent 的返回值来确定是调用子 View 的 dispatchTouchEvent 还是自身的 onTouchEvent, 并没有将调用交给 onInterceptTouchEvent

源码分析

事件是从 Activity 开始分发,Activity 的 dispatchTouchEvent 是如何接受到触摸事件,还有一系列的前期工作,后面会单独写一篇文章总结。

Activity 对事件的分发流程

Activity.dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 第一次按下时,用户希望与设备进行交互时,可以重写该方法实现
onUserInteraction();
}
// 获取PhoneWindow, 传递事件
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 没有任何View处理事件时,交给Activity的onTouchEvent处理
return onTouchEvent(ev);
}

其中 getWindow 返回的是 Activity 的 mWindow 成员变量,而 Window 类是一个抽象类,唯一实现类是 PhoneWindow,所以该方法获取到的是 PhoneWindow 对象。

getWindow().superDispatchTouchEvent

1
2
3
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

可以看到,PhoneWindow 中直接将事件交给了 DecorView 处理,DecorView 的 superDispatchTouchEvent 方法如下。

1
2
3
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

DecorView 调用的是父类的 dispatchTouchEvent 方法,而 DecorView 的父类是 ViewGroup,所以接着会调用 ViewGroup.dispatchTouchEvent

Activity.onTouchEvent

如果没有任何 View 处理事件,最后会交给 Activity 的 onTouchEvent 处理。

1
2
3
4
5
6
7
8
public boolean onTouchEvent(MotionEvent event) {
// 当窗口需要关闭时,消费掉当前事件
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}

ViewGroup 对事件的分发流程

ViewGroup.dispatchTouchEvent

ViewGroup 的 dispatchTouchEvent 方法内容较多,这里拆分为检测拦截、寻找子 View、分发事件。

1、 检测拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 检测拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 可以通过调用 requestDisallowInterceptTouchEvent,不让父 View 拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { // 允许父 View 拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 当前不是 ACTION_DOWN 事件,且没有触摸目标
// 即子视图如果不消费 ACTION_DOWN,那么后续事件也不会分发到,当前父视图拦截处理
intercepted = true;
}

这一段代码的目的是检测 ViewGroup 是否拦截事件。

mFirstTouchTarget 用来记录已经消费事件的子 View,目的是为了后续其他事件分发时直接将事件分发给 mFirstTouchTarget 指向的 View。

FLAG_DISALLOW_INTERCEPT 这个标志位可以影响到 ViewGroup 是否拦截事件,可以通过调用 requestDisallowInterceptTouchEvent 方法来设置,一般用于子 View 当中,禁止父 View 拦截事件,处理滑动冲突。但要注意,**requestDisallowInterceptTouchEvent 方法对 ACTION_DOWN 事件是无效的,为什么呢?因为 **ViewGroup 的 dispatchTouchEvent 方法每次接收到 ACTION_DOWN 事件时,都会初始化状态。代码如下:

1
2
3
4
5
6
7
// 处理一个初始按下事件 ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 发生 Action_DOWN 事件,取消清除之前所有的触摸目标
cancelAndClearTouchTargets(ev);
// 重置触摸状态,清除 FLAG_DISALLOW_INTERCEPT;设置 mFirstTouchTarget = null
resetTouchState();
}

2、寻找子 View

如果 ViewGroup 没有拦截事件,事件没有被取消,并且是 ACTION_DOWN 事件时,首先会去寻找可以接收事件的子 View。代码如下:

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
// 不取消事件,同时不拦截事件, 并且是Down事件才进入该区域
if (!canceled && !intercepted) {
// 寻找可以获取焦点的视图
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;

if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

// 清空早先的触摸对象
removePointersFromTouchTargets(idBitsToAssign);

final int childrenCount = mChildrenCount;
// 第一次 down 事件,同时子视图不为空时
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 从视图最上层到最下层,获取所有能接收该事件的子视图
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 从最底层的父视图开始遍历
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);

// 如果子view无法获取用户焦点,跳过本次循环
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 子view可见,并且没有播放动画;触摸事件的坐标落在子view的范围内
// 如果这两个条件有一项不满足,则跳过此次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

newTouchTarget = getTouchTarget(child);
// 子 view 已经接受触摸事件,将新指针id赋值给它,退出循环
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

resetCancelNextUpFlag(child);
// 如果触摸位置在子view的区域内,把事件分发给子view或者ViewGroup
// 实际调用的是chlid的dispatchTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 子view消费了事件
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 设置接收事件的子view为新的触摸目标,设置为触摸目标链表的头
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
// 子view处理了事件,就跳出for循环
break;
}

ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}

if (newTouchTarget == null && mFirstTouchTarget != null) {
// 将 mFirstTouchTarget 的链表最后的 TouchTarget 赋给 newToughTarget
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}

寻找子 View 进行分发事件的方法就是遍历子 View,有这样两个条件:

1
2
3
4
5
6
// 1、子view可见,并且没有播放动画;2、触摸事件的坐标落在子view的范围内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

这两个条件如果同时满足,则将事件分发给子 View。

接着会调用 dispatchTransformedTouchEvent 方法,可以猜到这个方法中肯定做了事件分发的操作。

如果这个方法返回 true,表示子 View 消费了事件,则会在 addTouchTarget 方法中设置 mFirstTouchTarget ,后续事件(ACTION_MOVE、ACTION_UP)分发时会直接将事件分发给 mFirstTouchTarget 指向的 View。

换句话说,如果子 View 没有消费 ACTION_DOWN 事件,mFirstTouchTarget 就会为 null,也就不会接收其他 ACTION_MOVE、ACTION_UP 等事件,如下代码:

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
if (mFirstTouchTarget == null) {
// 没有触摸target,则由当前ViewGroup来处理(第三个参数child为null)
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 如果View消费ACTION_DOWN事件,那么ACTION_MOVE、ACTION_UP等事件相继开始执行
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}

3、事件分发

dispatchTransformedTouchEvent 的伪代码如下:

1
2
3
4
5
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}

如果不存在子 View,ViewGroup 会调用父类 View 的 dispatchTouchEvent 方法,再调用 onTouchEvent 方法处理事件。

如果存在子 View ,将事件分发给子 View 的 dispatchTouchEvent,子 View 如果是 ViewGroup,则会调用 ViewGroup.dispatchTouchEvent,进行拦截检测、寻找子 View、分发事件操作。

View 对事件的分发流程

View.dispatchTouchEvent

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
public boolean dispatchTouchEvent(MotionEvent event) {
...

final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 在ACION_DOWN事件之前,如果存在滚动操作则停止。不存在则不进行操作
stopNestedScroll();
}

if (onFilterTouchEventForSecurity(event)) {
// 当存在OnTouchListener,且视图状态为ENABLED时,调用onTouch方法
// mOnTouchListener.onTouch优先于onTouchEvent执行
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true; // 如果已经消费事件,则返回True
}
//如果OnTouch没有消费Touch事件则调用OnTouchEvent
if (!result && onTouchEvent(event)) {
result = true; //如果已经消费事件,则返回True
}
}

if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}

// 处理取消或抬起操作
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}

return result;
}

如果存在 OnTouchListener,且视图状态为 ENABLED 时,调用onTouch 方法,onTouch 方法会优先处理事件。

如果 onTouch 方法返回 true,表示已经消费了事件,也就不再执行 onTouchEvent 。否则, onTouchEvent 处理事件,返回 true,消费事件,否则不处理事件。

View.onTouchEvent

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
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;

// 当View状态为DISABLED,如果可点击或可长按,则返回True,即消费事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}

if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

//当View状态为ENABLED,如果可点击或可长按,则返回True,即消费事件;
//与前面的的结合,可得出结论:只要view是可点击或可长按,则消费该事件.
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}

if (prepressed) {
setPressed(true, x, y);
}

if (!mHasPerformedLongPress) {
//这是Tap操作,移除长按回调方法
removeLongPressCallback();

if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//调用View.OnClickListener
if (!post(mPerformClick)) {
performClick();
}
}
}

if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}

if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}

removeTapCallback();
}
break;

case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;

if (performButtonActionOnTouchDown(event)) {
break;
}

//获取是否处于可滚动的视图内
boolean isInScrollingContainer = isInScrollingContainer();

if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//当处于可滚动视图内,则延迟TAP_TIMEOUT,再反馈按压状态
// 用来判断用户是否想要滚动。默认延时为100ms
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
//当不再滚动视图内,则立刻反馈按压状态
setPressed(true, x, y);
checkForLongClick(0); //检测是否是长按
}
break;

case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;

case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);

if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}

return true;
}
return false;
}

只要 view 是可点击或可长按,则消费该事件。

长按事件是在 ACTION_DOWN 事件中检测,单击事件需要两个事件 ACTION_DOWN、ACTION_UP 才能触发。

与 View 相关的各个方法调用顺序应该是这样的:

onTouchListener > onTouchEvent > onLongClickListener > onClickListener

总结

结合源码和事件分发示意图,对事件分发机制总结一下:

1、事件在从 Activity 的 dispatchTouchEvent 往下分发,如果没有 View 消费事件,事件最后会回到 Activity 的 onTouchEvent 方法处理。

2、ViewGroup 可以对事件进行拦截,拦截后事件不再往子 View 分发,交由发生拦截操作的 ViewGroup 的 onTouchEvent 处理。

3、子 View 可以调用 requestDisallowInterceptTouchEvent 方法进行设置,从而阻止父 ViewGroup 的 onInterceptTouchEvent 拦截事件。

4、如果 View 没有消费 ACTION_DOWN 事件,则之后的 ACTION_MOVE 等事件都不会再接收。

5、只要 View 是可点击或者可长按的,则消费该事件。

6、如果当前正在处理的事件被上层拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。

参考

Android 加固技术调研

第一代加固

第一代加固原理是基于 Dex 加载器的加固技术。

基本步骤

1、从 Apk 文件中获取原始的 dex 文件。

2、对原始的 dex 文件进行加密,并将加密后的 dex 文件存放在 asset 目录。

3、用脱壳 dex 文件替换原始 apk 文件的 dex 文件。脱壳 dex 文件的作用主要有两个:一是解密加密后的 dex 文件,二是动态加载解密后的 dex 文件。

4、修改清单文件,将程序入口改为壳程序。

5、打包签名。

缺陷

依赖 Java 的动态加载机制,解密后的 dex 文件必须解压到文件系统,即使是应用的私有目录,攻击者很容易获取文件。

第二代加固–不落地加载

相对于第一代加固,第二代加固在加载原始 dex 文件时采用的是内存加载方案。

即在解密原始 dex 文件后,不需要将 dex 写入文件系统,系统直接读取 dex 字节进行加载。

Android 底层支持内存加载 dex,但是 java 层未实现内存夹杂的接口,可以通过 jni 层调用底层的内存加载 dex 的函数。

市面上绝大多数第三方加固厂商的加密方案都是基于第二代加固技术。

缺陷

第二代加固方案能够防止第一代加固技术文件必须落地易被复制的缺陷,但是解密后的 dex 文件加载到内存后,在内存中是连续的 ,利用 gdb 等调试工具 dump 内存后可以直接找到原始 dex。

第三代加固–指令抽离

由于第二代加固技术仅仅对文件级别进行加密,其带来的问题是内存中的 Payload 是连续的,可以被攻击者轻易获取。第三代加固技术对这部分进行了改进,将保护级别降到了函数级别。

基本步骤

1、打包阶段将 Dex 文件中要保护的核心函数抽离出来生成另外一个文件。

2、运行阶段将函数内容重新恢复到对应的函数体。恢复的时间点有如下几种方式。

  • 加载之后恢复函数内容到 dex 壳所在的内存区域
  • 加载之后将函数内容恢复到虚拟机内部的结构体上:虚拟机读取 dex 文件后内部对每一个函数有一个结构体,这个结构体上有一个指针指向函数内容,可以通过修改这个指针修改对应的函数内容。
  • 拦截虚拟机与查找执行代码相关的函数,返回函数内容。

缺陷

攻击者可以通过自定义 Android 虚拟机,在解释器的代码上做记录一个函数的内容。接下来遍历所有函数,从而获取全部的函数内容。最终重组成一个完整的 dex 文件。

第四代加固–指令转换/VMP

第三代加固技术是函数级别的保护,使用 Android 虚拟机内的解释器执行代码,带来可能被记录的缺陷。

第四代加固技术使用自己的解释器来避免第三代的缺陷。

在编译打包的时候将 dex 的核心函数抽离,抽离后,翻译成一种自己定义的指令,用自己的一种编译指令进行翻译,把这个指令变一个种,变成其他的指令,这个时候运行的时候通过自己的解释器来解释执行,是自己定义的相关指令。在内存中运行的指令,在某些保护的函数里面就一定不是谷歌的标准指令了,这点能够很有效的防止内存直接拷贝等破解方案。

缺陷

其必须通过虚拟机提供的JNI接口与虚拟机进行交互,攻击者可以直接将指令转换/VMP 加固方案当作黑盒,通过自定义的 JNI 接口对象,对黑盒内部进行探测、记录和分析,进而得到完整 dex程序。

参考

APP加固技术历程及未来级别方案:虚机源码保护

《刷新》书摘

1、

在首堂正念训练课上,热尔韦博士问我们是否愿意先尝试一次特别的个人体验,大家全都点头表示同意。然后他要求我们中一人站出来当志愿者。结果没有人站出来,一时间房间里非常安静,气氛也非常尴尬。然后,我们的首席财务官埃米·胡德站了出来,她被要求背诵字母表,而且每个字母后面都要加入一个数字,即A1B2C3……让热尔韦博士感到奇怪的是:为什么不是每个人都站出来呢?这是一个高绩效团队吗?不是刚才每个人都说要尝试一下特别的体验吗?由于没有手机也没有电脑可看,所以我们低头看自己的鞋子,或看着同事紧张地微笑。这些问题的答案着实让人难以说出口,虽然它们就在嘴边。或是出于担心:担心被嘲笑,担心失败,担心被认为不是这个屋子里最聪明的人。或是出于傲慢:我的位置太重要了,不屑于玩这种游戏。“这是多么愚蠢的问题”,我们习惯于倾听。

2、

我们花太多的时间在工作上,所以工作应该有更深刻的意义。如果我们能够把个人的相信的价值与公司的长处结合起来,那么我们几乎就可以攻无不克了。

3、

只有经历过人生起伏,才能培养起同理心;要想不受苦受难,或者少受苦受难,就必须接纳无常。

4、

每一个人、每一个组织乃至每一个社会,在到达某一个点时,都应点击刷新——重新注入活力、重新激发生命力、重新组织并重新思考自己存在的意义。

5、

人要依照自己的意愿去做事,并遵循自己的节奏。当你依照自己的意愿做事时,节奏就上来了。只要是你喜欢的事,用心去做,把它做好,而且保持正当的目标,生活就不会辜负你。

6、

我再一次在对的时间出现在了对的地方。

7、

第一个原则是不遗余力地进行竞争,在面对不确定性和威胁时要充满激情。

第二个原则是要把团队放在优于个人地位和个人荣誉的位置上。

第三个原则是领导力的重要性。

8、

领导意味着做选择,然后将团队团结在这些选择周围。

9、

对领导而言,通过命令达成的共识并不是真正的共识。任何机构建设都源于清晰的、既能自上而下也能自下而上推动进步的愿景与文化。

10、

技术无非就是那些开发它的人的共同灵魂。

11、

史蒂夫·乔布斯(Steve Jobs)懂得什么是公司的灵魂。他曾经说过:“人类创作最根本的灵魂就是设计,而这个灵魂最终通过产品或服务的外在连续表现出来。”我同意他的观点。只要它内心的声音、动机是设计伟大的消费产品,那么苹果就会一直忠于它的灵魂。

12、

我们每天都要问一问自己:今天我在哪些方面保持着固化型思维?在哪些方面保持着成长型思维?

13、

抗拒变革的根本原因是对未知的恐惧。

14、

如果高层管理者都拿不出时间来发掘员工潜力,那么大多数公司团队成员的成长路径将会是静态的。

15、

各公司积极推动转型,确保自身的相关性和竞争力。而我们希望微软能够成为它们的合作伙伴。在这方面,每家公司都必须优先考虑四个方面。第一,利用数据提升客户体验,密切客户沟通。第二,在新的数字工作世界中,通过支持更大规模的、更多的移动生产力和移动协作,予力赋能员工。第三,优化运营,在销售、运营和财务等方面简化业务流程,并推动自动化。第四,转型产品模式、服务模式和业务模式。

16、

言论自由、隐私、安全和主权是永恒的、不容置疑的价值观。

17、

狂热支持一种或另一种价值观很容易,但这并不代表它就是对的。

18、

信任就像手里握着一只小鸟。握得太紧,会伤到小鸟;握得太松,小鸟就飞走了。

19、

预测未来的最好方法就是创造未来。

20、

多么不平等,经济学家们使用了意大利经济学家科拉多·基尼(CorradoGini)的一项研究成果。他在1912年发表了一个公式,可以用于计算如今被人称作“基尼系数”的指标,用于衡量一个社会的收入分配状况与绝对平等的收入分配状况之间存在多大差距。计算方法很简单。在特定人群中,如果100%的人都能每天赚1美元,那就是绝对平等。如果100%的人年收入达到100万美元,那也是绝对平等。但如果只有1%的人赚到100万美元,而其他人赚不到钱,那就接近绝对不平等。基尼的研究提供了一种方法,让我们可以衡量一个特定社会的收入分配接近或偏离绝对平等的程度。

21、

特定人群的基尼系数通常表示为分数。绝对平等表示为0,绝对不平等表示为1。在现实世界中,任何一个国家或地区的基尼系数都以这两个极端值之间的分数来表达。德国等欧洲发达国家的基尼系数几十年来一直徘徊在0.3左右。而美国的基尼系数已经上升了好几年,现在与中国和墨西哥差不多,都超过了0.4。

22、

Σ(教育+创新)×科技使用强度=经济增长

教育加创新,广泛应用于整个经济,尤其是那些具备比较优势的国家或地区,再乘以科技使用强度,久而久之,就会产生经济增长和生产力。

23、

“我为什么存在?”

“我们的机构为什么存在?”“跨国公司在这个世界上的角色是什么?”

“数字技术领导者的角色是什么,特别是在当今世界把科技作为推动增长的一个关键因素的时候?”

Java 反射基础

最近在调研 Android 应用加固方案,涉及大量反射技术,因此趁这个机会总结下 Java 反射的一些知识。

什么是反射?

反射是 Java 语言提供的一种基本功能。通过反射我们可以在运行时动态地操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造函数,甚至可以在运行时修改类定义。

基本使用方法

反射的主要步骤包括:

  • 获取目标类型的 Class 对象
  • 通过 Class 对象分别获取 Constructor 类对象、Method 类对象 和 Field 类对象。
  • 通过 Constructor 、Method 和 Field 分别获取目标类的构造函数、方法和属性的具体信息,并进行后续操作。

获取目标类型的 Class 对象

1、Object.getClass()

1
2
3
StringBuilder stringBuilder = new StringBuilder("123");
Class<?> classType = stringBuilder.getClass();
System.out.println(classType); // class java.lang.StringBuilder

2、T.class

1
2
Class<?> classType = int.class;
System.out.println(classType); // int

T 代表任意 Java 类型。

3、Class.forName()

1
2
3
4
5
6
7
Class<?> classType = null;
try {
classType = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(classType); // class java.lang.Integer

通过 Class 对象分别获取 Constructor 类对象、Method 类对象 和 Field 类对象

1、获取 Constructor 类对象

1
2
3
4
5
6
7
8
// a. 获取指定的构造函数 (公共 / 继承)
Constructor<T> getConstructor(Class<?>... parameterTypes)
// b. 获取所有的构造函数(公共 / 继承)
Constructor<?>[] getConstructors();
// c. 获取指定的构造函数 (不包括继承)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
// d. 获取所有的构造函数(不包括继承)
Constructor<?>[] getDeclaredConstructors();

2、获取 Method 类对象

1
2
3
4
5
6
7
8
// a. 获取指定的方法(公共 / 继承)
Method getMethod(String name, Class<?>... parameterTypes)
// b. 获取所有的方法(公共 / 继承)
Method[] getMethods()
// c. 获取指定的方法 ( 不包括继承)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
// d. 获取所有的方法( 不包括继承)
Method[] getDeclaredMethods()

3、获取 Field 类对象

1
2
3
4
5
6
7
8
// a. 获取指定的属性(公共 / 继承)
Field getField(String name) ;
// b. 获取所有的属性(公共 / 继承)
Field[] getFields() ;
// c. 获取指定的所有属性 (不包括继承)
Field getDeclaredField(String name)
// d. 获取所有的所有属性 (不包括继承)
Field[] getDeclaredFields()

以上方法中,不带 “Declared” 的方法返回某个类的公共方法或属性,继承的方法或属性;带 “Declared” 的方法返回公共、保护、默认(包)访问和私有方法或属性,但不包括继承的方法或属性。

通过 Constructor 、Method 和 Field 分别获取目标类的构造函数、方法和属性的具体信息,并进行后续操作

1、利用反射调用类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取 ConstructorClass 类的 Class 对象
Class clazz = ConstructorClass.class;
Object obj1 = clazz.getConstructor().newInstance(); // 输出:无参数构造函数
Object obj2 = clazz.getConstructor(String.class).newInstance("wuzy"); // 输出:有参数构造函数

// 目标类
class ConstructorClass {

public ConstructorClass() {
System.out.println("无参数构造函数");
}

public ConstructorClass(String str) {
System.out.println("有参数构造函数");
}
}

newInstance() 调用默认构造函数,若目标类无构造函数,则抛出异常 NoSuchMethodException。

2、利用反射调用类对象的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1、获取 MethodClass 类的 Class 对象
Class<?> clazz = MethodClass.class;
// 2、通过 Class 创建 MethodClass 对象
Object object = clazz.newInstance();
// 3、通过 Class 对象获取 add 方法
Method method = clazz.getMethod("add", int.class, int.class);
// 4、通过 Method 调用 add 方法
Object result = method.invoke(object,1,4);
System.out.println(result); // 输出 5

// 目标类
class MethodClass {

public MethodClass() {
}

public int add(int a, int b) {
return a + b;
}
}

3、利用反射获取类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Class clazz = FieldClass.class;
Object object = clazz.newInstance();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(object, "wuzy");
System.out.println(field.get(object)); // 输出: wuzy

// 目标类
class FieldClass {

public FieldClass() {}

private String name;
}

其中, setAccessible 用于屏蔽 Java 语言的访问检查,设为 true 可以访问类的私有属性、方法。

反射的使用场景

1、工厂模式:Factory 类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类 Factory 了

2、数据库 JDBC 中通过 Class.forName(Driver) 来获得数据库连接驱动

3、访问一些不能访问的变量或属性:破解别人代码。

4、实现动态代理。

以上就是反射的基本知识点,需要注意的是由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。通过反射实现动态代理和工厂模式会在后续文章中专门撰写。