1. Android 中创建定时任务的方式
Timer
(Java)- 缺点:明显的短板是,它不太适合那些需要长期在后台运行的定时任务。因为休眠策略,Android 手机会在长时间不操作的情况下自动让 CPU 进入到睡眠状态,这就有可能导致 Timer 中的定时任务无法正常运行
Alarm
(Android)- 特点:Alarm 具有唤醒 CPU 的功能,它可以保证在大多数情况下需要执行定时任务的时候 CPU 都能正常工作。需要注意的是,唤醒 CPU 和唤醒屏幕完全不是一个概念
2. 写一个使用 Alarm
机制创建定时任务的 Demo
Demo
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
39public class LongRunningService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() { // 开启一个子线程执行定时任务,这比放在主线程更加准确,因为逻辑操作也是耗时的
@Override
public void run() {
// 在这里执行具体的逻辑操作
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); // 获取一个 AlarmManager 实例
int anHour = 60 * 60 * 1000;
// 使用 SystemClock.elapsedRealtime() 方法可以获取到系统开机至今所经历时间的毫秒数
// 使用 System.currentTimeMillis() 方法可以获取到 1970 年 1 月 1 日 0 点至今所经历时间的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this, LongRunningService.class);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
// 第一个参数是一个整型参数,用于指定 AlarmManager 的工作类型,有 4 种值可选
// ELAPSED_REALTIME:表示让定时任务的出发时间从系统开机开始算起,但不会唤醒 CPU
// ELAPSED_REALTIME_WAKEUP:同样表示让定时任务的触发时间从系统开机开始算起,但会唤醒 CPU
// RTC:表示让定时任务的触发时间从 1970 年1 月 1 日 0 点开始算起,但不会唤醒 CPU
// RTC_WAKEUP:同样表示让定时任务的触发时间从 1970 年 1 月 1 日 0 点开始算起,但会唤醒 CPU
// 第二个参数是定时任务的触发时间,以毫秒为单位。要和第一个参数匹配,即开机配开机,RTC 配 RTC
// 所以,也可以写成:long triggerTime = System.currentTimeMillis() + 10 * 1000; manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent);
// 第三个参数是一个 PendingIntent,在创建通知那一节里第一次用到用于给通知增加点击功能
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi); // 在后台服务中每隔一小时执行一次定时任务
return super.onStartCommand(intent, flags, startId);
}
}
// 启动定时服务
Intent intent = new Intent(context, LongRunningService.class);
context.startService(intent);优化
- 从 Android 4.4 系统开始,Alarm 任务的触发时间将会变得不准确,有可能会延迟一段时间后任务才能得到执行
- 这并不是个 bug,而是系统在耗电性方面进行的优化。系统会自动检测目前有多少 Alarm 任务存在,然后将触发时间相近的几个任务放在一起执行,这就可以大幅度减少 CPU 被唤醒的次数,从而有效延长电池的使用时间
更准确的方案
- 如果要求
Alarm
任务的执行时间必须准确无误,Android 仍然提供了解决方案:使用AlarmManager
的setExact()
方法来代替set()
方法,就基本上可以保证任务能够准时执行了 - 如果要求
Alarm
任务即使在Doze
模式下也必须正常执行,Android 还是提供了解决方案:调用AlarmManager
的setAndAllowWhileIdle()
或setExactAndAllowWhileIdle()
方法就能让定时任务即使在Doze
模式下也能正常执行
- 如果要求
3. 什么是 Doze 模式
在 Android 6.0 系统中,Google 加入了一个全新的
Doze
模式,可以大幅延长电池的使用寿命Doze
模式- 当用户设备是 Android 6.0 或以上系统时,如果该设备未插接电源,处于静止状态(Android 7.0 中删除了这一条件),且屏幕关闭了一段时间后,就会进入
Doze
模式 - 在 Doze 模式下,系统会对 CPU、网络、Alarm 等活动进行限制,从而延长了电池的使用寿命
- 系统不会一直处于
Doze
模式,而是会间歇性地退出Doze
模式一小段时间,在这段时间中,应用就可以去完成它们的同步操作、Alarm
任务等。随着设备进入Doze
模式的时间越长,间歇性退出Doze
模式的时间间隔也会越长,从而更好地把控电池使用寿命
- 当用户设备是 Android 6.0 或以上系统时,如果该设备未插接电源,处于静止状态(Android 7.0 中删除了这一条件),且屏幕关闭了一段时间后,就会进入
Doze
模式下受限的功能- 网络访问被禁止
- 系统忽略唤醒 CPU或者屏幕操作
- 系统不再执行 WIFI 扫描
- 系统不再执行同步服务
- Alarm 任务将会在下次退出
Doze
模式的时候执行
4. 什么是多窗口模式
多窗口模式:允许用户在同一个屏幕中同时打开两个应用程序
进入方式
- 在
Overview
列表界面长按任意一个活动的标题,将该活动拖动到屏幕突出显示的区域,就可以进入多窗口模式 - 打开任意一个程序,长按
Overview
按钮,也可以进入多窗口模式
- 在
退出方式
- 再次长按
Overview
按钮 - 将屏幕中央的分隔线向屏幕任意一个方向拖动到底
- 再次长按
多窗口模式下的生命周期
- 多窗口模式并不会改变活动原有的生命周期,只是会将用户最近交互过的那个活动设置为运行状态,而将另外一个可见活动设置为暂停状态
- 如果这时用户又去和暂停的活动进行交互,那么该活动就变成运行状态,之前处于运行状态的活动变成暂停状态
- 进入多窗口模式的活动和横竖屏切换都是默认重新创建活动的,因为活动的大小发生了比较大的变化
- 需要注意的是,在多窗口模式下,用户仍然可以看到处于暂停状态的应用,所以像抖音、快手、直播等视频类的应用在此时就应该能继续播放视频才对。因此,最好不要在活动的
onPause()
方法中去处理视频播放器的暂停逻辑,而是应该在onStop()
方法中去处理,并且在onStart()
方法恢复视频的播放
改变多窗口模式下活动被重新创建这一默认行为
- 在
AndroidManifest.xml
文件中对活动做如下配置:"android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
> - 配置之后,不管是进入多窗口模式,还是横竖屏切换,活动都不会被重新创建,而是会将屏幕发生变化的事件通知到
Activity
的onConfigurationChanged()
方法中,所以,如有需要可以重写这个方法
- 在
禁用多窗口模式
- 禁用方式:在
AndroidManifest.xml
的<application>
或<activity>
标签中加入如下属性即可:android:resizeableActivity="false"
。如果不配置,默认值为true
- 局限:属性
android:resizeableActivity
只有在项目的targetSdkVersion
指定成 24 或更高的时候才会有用,否则该属性无效(即使进入多窗口模式,但可能无法正常工作) - 解决方案:Android 规定,如果项目指定的
targetSdkVersion
低于 24,并且活动是不允许横竖屏切换的,那么该应用也将不支持多窗口模式 - 禁用横竖屏切换:默认情况下应用是允许横竖屏切换的,如果要禁用,可以在
AndroidManifest.xml
中配置:android:screenOrientation="portrait"
(该属性还有很多其他值可选,但最常用的就是portrait
和landscape
)
- 禁用方式:在
5. Lambda 表达式在 Android 中的应用
Java 8 中的特色功能有:Lambda 表达式、Stream API、接口默认实现等
- Stream API 和接口默认实现等特性都只支持 Android 7.0 及以上的系统
- Lambda 表达式最低兼容到 Android 2.3 系统
Lambda 表达式:本质上是一种匿名方法,没有方法名,也没有访问修饰符和返回值类型,使用它来编写代码将会更加简洁易读(相对而言)
Android 中使用 Lambda 表达式
Lambda 适用于函数式接口,即只有一个方法的接口(开启线程、点击事件等)
在
app/build.gradle
中添加如下配置1
2
3
4
5
6
7
8
9
10
11android {
...
defaultConfig {
...
true =
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}Demo1
1
2
3
4
5
6
7
8
9
10
11
12// 传统方式
new Thread(new Runnable() {
@Override
public void run() {
//处理具体的逻辑
}
}).start();
// 使用 Lambda 表达式
new Thread(() -> {
// 处理具体的逻辑
}).start();Demo2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 自定义的函数式接口,且方法有参数和返回值
public interface MyListener {
String doSomething(String a, int b);
}
// 使用 Lambda 表达式创建 MyListener 接口的匿名实现
MyListener listener = (String a, int b) -> {
String result = a + b;
return result;
}
// 简化版本
MyListener listener = (a, b) -> {
String result = a + b;
return result;
}Demo3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 给按钮设置点击事件
// 传统方式
Button button = (Button) fiindViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击事件
}
});
// 使用 Lambda 表达式
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener((v) -> {
// 处理点击事件
});
// 简化版本:当接口的实现方法有且只有一个参数的时候,还可以将参数外的括号省略
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(v -> {
// 处理点击事件
});
6. ART 与 Dalvik 概述
从 Android 5.0 (Lollipop)开始,Android Runtime(ART)就彻底代替了原先的 Dalvik,成为 Android 系统上新的虚拟机
ART 对 Dalvik 的改进
- Dalvik 是为 32 位设计的,不适用于 64 位 CPU
- 单纯的字节码解释加 JIT 编译的执行方式,性能要弱于本地机器码的执行
- 无论是解释执行还是 JIT 编译都是单次运行过程中发生,每运行一次都可能需要重新做这些工作,浪费资源
- 原先的垃圾回收机制不够好,会导致卡顿
除了 64 位,ART 最主要的两项改进
- AOT 编译:Ahead-of-time(AOT) 是相对于 Just-in-time(JIT) 而言的。JIT 是在运行时进行字节码到本地机器码的编译,这也是为什么 Java 普遍被认为效率比 C++ 差的原因。无论是解释器的解释,还是运行过程中即时编译,都比 C++ 编译出来的本地机器码执行多了一个耗费时间的过程。而 AOT 就是向 C++ 编译过程靠拢的一项技术:当 APK 在安装的时候,系统会通过一个名词为 dex2oat 的工具将 APK 中的 dex 文件编译成包含本地机器码的 oat 文件存放下来。这样做之后,在程序执行的时候,就可以直接使用已经编译好的机器码以加快效率
- 垃圾回收的改进:GC 是虚拟机非常重要的一个特性,因为它的实现好坏会影响所有在虚拟机上运行的应用。GC 实现得不好可能会导致画面跳跃、掉帧、UI 响应过慢等问题。ART 的垃圾回收机制相较于 Dalvik 虚拟机有如下改进
- 将 GC 的停顿由 2 次改成 1 次
- 在仅有的一次 GC 停顿中进行并行处理
- 在特殊场景下,对近期创建的具有较短生命的对象消耗更少的时间进行垃圾回收
- 改进垃圾收集的工效,更频繁地执行并行垃圾收集
- 对于后台进程的内存在垃圾回收过程中进行压缩以解决碎片化问题