0%

Android 详解新特性

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
    39
    public 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 仍然提供了解决方案:使用 AlarmManagersetExact() 方法来代替 set() 方法,就基本上可以保证任务能够准时执行了
    • 如果要求 Alarm 任务即使在 Doze 模式下也必须正常执行,Android 还是提供了解决方案:调用 AlarmManagersetAndAllowWhileIdle()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 模式的时间间隔也会越长,从而更好地把控电池使用寿命
  • Doze 模式下受限的功能

    • 网络访问被禁止
    • 系统忽略唤醒 CPU或者屏幕操作
    • 系统不再执行 WIFI 扫描
    • 系统不再执行同步服务
    • Alarm 任务将会在下次退出 Doze 模式的时候执行

4. 什么是多窗口模式

  • 多窗口模式:允许用户在同一个屏幕中同时打开两个应用程序

  • 进入方式

    • Overview 列表界面长按任意一个活动的标题,将该活动拖动到屏幕突出显示的区域,就可以进入多窗口模式
    • 打开任意一个程序,长按 Overview 按钮,也可以进入多窗口模式
  • 退出方式

    • 再次长按 Overview 按钮
    • 将屏幕中央的分隔线向屏幕任意一个方向拖动到底
  • 多窗口模式下的生命周期

    • 多窗口模式并不会改变活动原有的生命周期,只是会将用户最近交互过的那个活动设置为运行状态,而将另外一个可见活动设置为暂停状态
    • 如果这时用户又去和暂停的活动进行交互,那么该活动就变成运行状态,之前处于运行状态的活动变成暂停状态
    • 进入多窗口模式的活动和横竖屏切换都是默认重新创建活动的,因为活动的大小发生了比较大的变化
    • 需要注意的是,在多窗口模式下,用户仍然可以看到处于暂停状态的应用,所以像抖音、快手、直播等视频类的应用在此时就应该能继续播放视频才对。因此,最好不要在活动的 onPause() 方法中去处理视频播放器的暂停逻辑,而是应该在 onStop() 方法中去处理,并且在 onStart() 方法恢复视频的播放
  • 改变多窗口模式下活动被重新创建这一默认行为

    • AndroidManifest.xml 文件中对活动做如下配置:"android:configChanges="orientation|keyboardHidden|screenSize|screenLayout">
    • 配置之后,不管是进入多窗口模式,还是横竖屏切换,活动都不会被重新创建,而是会将屏幕发生变化的事件通知到 ActivityonConfigurationChanged() 方法中,所以,如有需要可以重写这个方法
  • 禁用多窗口模式

    • 禁用方式:在 AndroidManifest.xml<application><activity> 标签中加入如下属性即可:android:resizeableActivity="false"。如果不配置,默认值为 true
    • 局限:属性 android:resizeableActivity 只有在项目的 targetSdkVersion 指定成 24 或更高的时候才会有用,否则该属性无效(即使进入多窗口模式,但可能无法正常工作)
    • 解决方案:Android 规定,如果项目指定的 targetSdkVersion 低于 24,并且活动是不允许横竖屏切换的,那么该应用也将不支持多窗口模式
    • 禁用横竖屏切换:默认情况下应用是允许横竖屏切换的,如果要禁用,可以在 AndroidManifest.xml 中配置:android:screenOrientation="portrait"(该属性还有很多其他值可选,但最常用的就是 portraitlandscape

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
      11
      android {
      ...
      defaultConfig {
      ...
      jackOptions.enabled = 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 停顿中进行并行处理
      • 在特殊场景下,对近期创建的具有较短生命的对象消耗更少的时间进行垃圾回收
      • 改进垃圾收集的工效,更频繁地执行并行垃圾收集
      • 对于后台进程的内存在垃圾回收过程中进行压缩以解决碎片化问题

7. Android 10.0 中的新特性

-------------------- 本文结束感谢您的阅读 --------------------