Android 详解新特性

1. Android 中创建定时任务的方式?

答:Android 中的定时任务一般有两种实现方式,一种是使用 Java API 里提供的 Timer 类;一种是使用 Android 中的 Alarm 机制。这两种方式在大多数情况下都能实现类似的效果。

  • Timer

    • 缺点:明显的短板是,它不太适合那些需要长期在后台运行的定时任务。因为休眠策略Android 手机会在长时间不操作的情况下自动让 CPU 进入到睡眠状态,这就有可能导致 Timer 中的定时任务无法正常运行。
  • Alarm

    • 特点:Alarm 具有唤醒 CPU 的功能,它可以保证在大多数情况下需要执行定时任务的时候 CPU 都能正常工作。需要注意的是,唤醒 CPU 和唤醒屏幕完全不是一个概念。

2. 【笔试题】手写一个使用 Alarm 机制创建定时任务的 Demo?

答:

  • Demo

    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 中添加如下配置

      android {
          ...
          defaultConfig {
              ...
              jackOptions.enabled = true
          }
          compileOptions {
              sourceCompatibility JavaVersion.VERSION_1_8
              targetCompatibility JavaVersion.VERSION_1_8
          }
      }
      
    • Demo1

      // 传统方式
      new Thread(new Runnable() {
          @Override
          public void run() {
              // 处理具体的逻辑
          }
      }).start();
      
      // 使用 Lambda 表达式
      new Thread(() -> {
          // 处理具体的逻辑
      }).start();
      
    • Demo2

      // 自定义的函数式接口,且方法有参数和返回值
      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

      // 给按钮设置点击事件
      
      // 传统方式
      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. Android 10.0 中的新特性?

答:参考 Android 10.0 来了,居然有这新特性

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