让应用始终显示在 Wear 上

当用户不再使用手表时,Wear OS 会自动处理处于活跃状态的应用,使其进入低功耗模式。这称为系统氛围模式。如果用户在一定时间范围内再次与手表进行互动,Wear OS 会从用户上次离开的位置进入应用。

对于特定用例(例如,用户想在跑步期间查看心率和步速),您还可以控制在低功耗模式(氛围模式)下显示的内容。同时在氛围模式和互动模式下运行的 Wear OS 应用称为始终开启的应用。

让应用始终可见会影响电池续航时间,因此在为应用添加这项功能时,应该考虑这个影响。

配置项目

如需支持氛围模式,请按以下步骤操作:

  1. 根据创建和运行穿戴式应用页面上的配置,创建或更新您的项目。
  2. 向 Android 清单文件添加 WAKE_LOCK 权限:
<uses-permission android:name="android.permission.WAKE_LOCK" />

使用 AmbientModeSupport 类支持氛围模式

如需使用 AmbientModeSupport 类,请执行以下操作:

  1. 创建子类 FragmentActivity 或其任一子类。
  2. 实现 AmbientCallbackProvider 接口,如以下示例所示。替换 getAmbientCallback() 方法,以提供对来自 Android 系统的氛围事件做出反应所需的回调。在第 4 步中,您将创建自定义回调类。

    Kotlin

    class MainActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbackProvider {
        …
        override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = MyAmbientCallback()
        …
    }
    

    Java

    public class MainActivity extends AppCompatActivity implements AmbientModeSupport.AmbientCallbackProvider {
        …
        @Override
        public AmbientModeSupport.AmbientCallback getAmbientCallback() {
            return new MyAmbientCallback();
        }
        …
    }
    
  3. onCreate() 方法中,通过调用 AmbientModeSupport.attach(FragmentActivity) 启用氛围模式。此方法会返回一个 AmbientModeSupport.AmbientController。您可以通过该控制器检查回调之外的氛围状态。您可能需要保留对 AmbientModeSupport.AmbientController 对象的引用:

    Kotlin

    class MainActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbackProvider {
        ...
        /*
         * Declare an ambient mode controller, which will be used by
         * the activity to determine if the current mode is ambient.
         */
        private lateinit var ambientController: AmbientModeSupport.AmbientController
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            ...
            ambientController = AmbientModeSupport.attach(this)
        }
        ...
    }
    

    Java

    public class MainActivity extends AppCompatActivity implements AmbientModeSupport.AmbientCallbackProvider {
        ...
        /*
         * Declare an ambient mode controller, which will be used by
         * the activity to determine if the current mode is ambient.
         */
        private AmbientModeSupport.AmbientController ambientController;
        ...
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ...
            ambientController = AmbientModeSupport.attach(this);
        }
        ...
    }
    
  4. 创建一个扩展 AmbientCallback 类的内部类,以便对氛围事件进行操作。此类将成为从您在第 2 步中创建的方法返回的对象:

    Kotlin

    private class MyAmbientCallback : AmbientModeSupport.AmbientCallback() {
    
        override fun onEnterAmbient(ambientDetails: Bundle?) {
          // Handle entering ambient mode
        }
    
        override fun onExitAmbient() {
          // Handle exiting ambient mode
        }
    
        override fun onUpdateAmbient() {
          // Update the content
        }
    }
    

    Java

    private class MyAmbientCallback extends AmbientModeSupport.AmbientCallback {
        @Override
        public void onEnterAmbient(Bundle ambientDetails) {
          // Handle entering ambient mode
        }
    
        @Override
        public void onExitAmbient() {
          // Handle exiting ambient mode
         }
    
        @Override
        public void onUpdateAmbient() {
          // Update the content
        }
    }
    

请查看 GitHub 上的 AlwaysOnKotlin 示例,以了解详细信息和最佳做法。

使用 WearableActivity 类支持氛围模式

对于新项目和现有项目,您可以通过更新项目配置来向 Wear 应用添加氛围模式支持。

创建支持氛围模式的 activity

您可以使用 WearableActivity 类在 activity 中启用氛围模式:

  1. 创建扩展 WearableActivity 的 activity。
  2. 在您的 activity 的 onCreate() 方法中,调用 setAmbientEnabled() 方法。

按以下方式在 activity 中启用氛围模式:

Kotlin

class MainActivity : WearableActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setAmbientEnabled()
    ...
    }
}

Java

public class MainActivity extends WearableActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setAmbientEnabled();
        ...
    }

处理模式之间的转换

如果用户在应用显示期间有一段时间未与应用进行互动,或者屏幕被覆盖,系统就会将 activity 切换到氛围模式。

在应用切换到氛围模式后,将 activity 界面更新为更基本的布局以减少耗电量。请使用黑色背景和最简单的白色图形和文本。

为了简化从互动模式到氛围模式的转换,请尽量在屏幕上保持相似的项目布局。

注意:在氛围模式下,应停用屏幕上的所有互动元素,如按钮。

当 activity 切换到氛围模式时,系统会调用氛围回调的 onEnterAmbient() 方法。以下代码段展示了如何在系统切换到氛围模式后将文本颜色更改为白色并停止进行抗锯齿处理:

Kotlin

override fun onEnterAmbient(ambientDetails: Bundle?) {
    super.onEnterAmbient(ambientDetails)

    stateTextView.setTextColor(Color.WHITE)
    stateTextView.paint.isAntiAlias = false
}

Java

@Override
public void onEnterAmbient(Bundle ambientDetails) {
    super.onEnterAmbient(ambientDetails);

    stateTextView.setTextColor(Color.WHITE);
    stateTextView.getPaint().setAntiAlias(false);
}

当用户点按屏幕或抬起手腕时,activity 会从范围模式切换到互动模式。系统会调用 onExitAmbient() 方法。替换此方法以更新界面布局,让您的应用在全彩色互动状态下显示。

以下代码段展示了如何在系统切换到互动模式后将文本颜色更改为绿色并启用抗锯齿处理:

Kotlin

override fun onExitAmbient() {
    super.onExitAmbient()

    stateTextView.setTextColor(Color.GREEN)
    stateTextView.paint.isAntiAlias = true
}

Java

@Override
public void onExitAmbient() {
    super.onExitAmbient();

    stateTextView.setTextColor(Color.GREEN);
    stateTextView.getPaint().setAntiAlias(true);
}

在氛围模式下更新内容

在氛围模式下,您可以更新屏幕来向用户提供新消息,但必须审慎处理,以在显示更新与电池续航时间之间取得平衡。强烈建议您考虑仅替换 onUpdateAmbient() 方法,以便在氛围模式下每隔一分钟或更短时间更新一次屏幕。

如需更新应用内容,请替换氛围回调中的 onUpdateAmbient() 方法:

Kotlin

override fun onUpdateAmbient() {
    super.onUpdateAmbient()
    // Update the content
}

Java

@Override
public void onUpdateAmbient() {
    super.onUpdateAmbient();
    // Update the content
}

在氛围模式下更新 Wear OS 应用的频率可以高于每分钟一次,不过不建议这样做。对于需要更频繁地更新的应用,您可以使用 AlarmManager 对象唤醒处理器并更频繁地更新屏幕。

如需实现一个在氛围模式下更频繁地更新内容的闹钟,请按以下步骤操作:

  1. 准备闹钟管理器。
  2. 设置更新的频率。
  3. 检查设备当前是否处于氛围模式,或者在 activity 切换到氛围模式时安排下一次更新的时间。
  4. 当 activity 切换到互动模式或停止时取消闹钟。

注意:闹钟管理器可能会在触发时创建 activity 的新实例。为防止出现这种情况,请确保在清单中使用 android:launchMode="singleInstance" 参数声明您的 activity。

下面几部分将详细介绍这些步骤。

准备闹钟管理器

闹钟管理器会启动一个 PendingIntent,用来更新屏幕并安排下次闹钟时间。以下示例展示了如何在 activity 的 onCreate() 方法中声明闹钟管理器和待定 intent:

Kotlin

// Action for updating the display in ambient mode, per our custom refresh cycle.
private const val AMBIENT_UPDATE_ACTION = "com.your.package.action.AMBIENT_UPDATE"
...
private lateinit var ambientUpdateAlarmManager: AlarmManager
private lateinit var ambientUpdatePendingIntent: PendingIntent
private lateinit var ambientUpdateBroadcastReceiver: BroadcastReceiver

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setAmbientEnabled()

    ambientUpdateAlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

    ambientUpdatePendingIntent = Intent(AMBIENT_UPDATE_ACTION).let { ambientUpdateIntent ->
        PendingIntent.getBroadcast(this, 0, ambientUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT)
    }

    ambientUpdateBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            refreshDisplayAndSetNextUpdate()
        }
    }
    ...
}

Java

// Action for updating the display in ambient mode, per our custom refresh cycle.
private static final String AMBIENT_UPDATE_ACTION = "com.your.package.action.AMBIENT_UPDATE";

private AlarmManager ambientUpdateAlarmManager;
private PendingIntent ambientUpdatePendingIntent;
private BroadcastReceiver ambientUpdateBroadcastReceiver;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setAmbientEnabled();

    ambientUpdateAlarmManager =
        (AlarmManager) getSystemService(Context.ALARM_SERVICE);

    Intent ambientUpdateIntent = new Intent(AMBIENT_UPDATE_ACTION);

    ambientUpdatePendingIntent = PendingIntent.getBroadcast(
        this, 0, ambientUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    ambientUpdateBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            refreshDisplayAndSetNextUpdate();
        }
    };
    ...
}

使用 onResume()onPause() 注册和取消注册广播接收器

Kotlin

override fun onResume() {
    super.onResume()
    IntentFilter(AMBIENT_UPDATE_ACTION).also { filter ->
        registerReceiver(ambientUpdateBroadcastReceiver, filter)
    }
}

override fun onPause() {
    super.onPause()
    unregisterReceiver(ambientUpdateBroadcastReceiver)
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
}

Java

@Override
public void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter(AMBIENT_UPDATE_ACTION);
    registerReceiver(ambientUpdateBroadcastReceiver, filter);
        ...
}

@Override
public void onPause() {
    super.onPause();
    unregisterReceiver(ambientUpdateBroadcastReceiver);
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
    ...
}
更新屏幕并安排数据更新

在此示例 activity 中,闹钟管理器在氛围模式下每 20 秒触发一次。当计时器开始计时的时候,闹钟就会触发 intent 来更新屏幕,然后设置下次更新的延迟。

以下示例展示了如何更新屏幕上的信息并为下次更新设置闹钟:

Kotlin

// Milliseconds between waking processor/screen for updates
private val AMBIENT_INTERVAL_MS: Long = TimeUnit.SECONDS.toMillis(20)
...
private fun refreshDisplayAndSetNextUpdate() {
    if (isAmbient) {
        // Implement data retrieval and update the screen for ambient mode
    } else {
        // Implement data retrieval and update the screen for interactive mode
    }
    val timeMs: Long = System.currentTimeMillis()
    // Schedule a new alarm
    if (isAmbient) {
        // Calculate the next trigger time
        val delayMs: Long = AMBIENT_INTERVAL_MS - timeMs % AMBIENT_INTERVAL_MS
        val triggerTimeMs: Long = timeMs + delayMs
        ambientUpdateAlarmManager.setExact(
                AlarmManager.RTC_WAKEUP,
                triggerTimeMs,
                ambientUpdatePendingIntent)
    } else {
        // Calculate the next trigger time for interactive mode
    }
}

Java

// Milliseconds between waking processor/screen for updates
private static final long AMBIENT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
private void refreshDisplayAndSetNextUpdate() {
    if (isAmbient()) {
        // Implement data retrieval and update the screen for ambient mode
    } else {
        // Implement data retrieval and update the screen for interactive mode
    }
    long timeMs = System.currentTimeMillis();
    // Schedule a new alarm
    if (isAmbient()) {
        // Calculate the next trigger time
        long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
        long triggerTimeMs = timeMs + delayMs;
        ambientUpdateAlarmManager.setExact(
            AlarmManager.RTC_WAKEUP,
            triggerTimeMs,
            ambientUpdatePendingIntent);
    } else {
        // Calculate the next trigger time for interactive mode
    }
}
安排下次闹钟时间

通过替换 onEnterAmbient() 方法和 onUpdateAmbient() 方法,安排更新屏幕的闹钟时间,如以下代码示例所示:

Kotlin

override fun onEnterAmbient(ambientDetails: Bundle?) {
    super.onEnterAmbient(ambientDetails)

    refreshDisplayAndSetNextUpdate()
}

override fun onUpdateAmbient() {
    super.onUpdateAmbient()
    refreshDisplayAndSetNextUpdate()
}

Java

@Override
public void onEnterAmbient(Bundle ambientDetails) {
    super.onEnterAmbient(ambientDetails);
    refreshDisplayAndSetNextUpdate();
}

@Override
public void onUpdateAmbient() {
    super.onUpdateAmbient();
    refreshDisplayAndSetNextUpdate();
}

注意:在本例中,每当需要更新屏幕时,就会调用 refreshDisplayAndSetNextUpdate() 方法。如需查看关于何时调用此方法的更多示例,请参阅 GitHub 上的 AlwaysOnKotlin 示例

取消闹钟

当设备切换到互动模式时,在 onExitAmbient() 方法中取消闹钟:

Kotlin

override fun onExitAmbient() {
    super.onExitAmbient()

    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
}

Java

@Override
public void onExitAmbient() {
    super.onExitAmbient();
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
}

当用户退出或停止 activity 时,在 activity 的 onDestroy() 方法中取消闹钟:

Kotlin

override fun onDestroy() {
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
    super.onDestroy()
}

Java

@Override
public void onDestroy() {
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
    super.onDestroy();
}