【Unity】Android,iOS端末のバッテリー残量と充電状態の取得

はじめに

はじめまして、Unityエンジニアの泉川です。
この記事はカヤックUnityアドベントカレンダー2018の12日目の記事になります。
今回はUnityで端末のバッテリー残量と充電状態の取得について書いていこうと思います。

f:id:izumikawa-yuichi:20181127182447j:plain

この画面の右上のやつです。

経緯とか

アプリで遊んでいて唐突に画面の上から出てくる「バッテリー残量が低下しています」の通知。
心臓に悪いですね。
避けるためにちょいちょい端末の頭をフリックするのも馬鹿らしい。
そうなった時にどうするかは取りあえず3つあると思います。
1. ステータスバーを常に表示する
2. アプリ内で表示できるようにする
3. 諦める

上記3つのパターンで考えた時のメリットデメリットを考えてみましょう。

方法 メリット デメリット
ステータスバーを常に表示する ついでにいろんな情報が見れる
見慣れている
画面が狭くなる
デザインが浮く可能性がある
アプリ内で表示できるようにする デザイナーがデザインできる
端末依存が少ない
実装が必要
諦める 工数がかからない 問題が解決していない

で、「アプリ内で表示できるようにする」という方法を取る場合ですが、Unity 2018以前でAndroid向けに実装するには、javaのコードをプラグインとしてビルドした上でプロジェクトに入れる必要がありました。
ビルドのためにAndroid Studioの環境を用意したり、コード修正のたびにプラグインの再ビルドが必要になったりと、手間のかかるものでした。
しかし、Unity2018からはjavaファイルを直接Unityに置いておくだけで済むようになり作業がだいぶ減りました。
というわけで今回はUnity2018を使って、アプリ内にバッテリ残量を表示してやろうという流れです。

バージョン

今回はUnity2018.2.14f1を対象に記事を書いています。

やり方

各ファイルの場所

.csは何処でもいいです。
f:id:izumikawa-yuichi:20181127182423p:plain

BatteryOverseer.java

package com.kayac.uac;

import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.app.Activity;
import android.content.IntentFilter;

import com.unity3d.player.UnityPlayer;

public class  BatteryOverseer
{
    /**
     * バッテリーレベルは現在の充電量に当たります
     */
    public static int BatteryLevel(){
        final Intent batteryStatus = GetBatteryStatus();

        return batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    }

    /**
     * バッテリースケールは最大の充電量に当たります
     */
    public static int BatteryScale(){
        final Intent batteryStatus = GetBatteryStatus();

        return batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    }

    /**
     * バッテリーステータスは現在の充電ステータスに当たります
     */
    public static int BatteryStatus(){
        final Intent batteryStatus = GetBatteryStatus();

        return batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
    }

    /**
     * バッテリー状態を取得するためのIntentを取得するメソッドです
     */
    private static Intent GetBatteryStatus(){
        // アプリのContextを取得する
        final Activity activity = UnityPlayer.currentActivity;
        final Context context = activity.getApplicationContext();
        // バッテリー状態を取得するためのIntentを取得
        final IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        final Intent batteryStatus = context.registerReceiver(null, ifilter);

        return batteryStatus;
    }
}

ref:
https://developer.android.com/training/monitoring-device-state/battery-monitoring?hl=ja

BatteryOverseer.mm

extern "C"
{
    /**
     * バッテリーレベルは現在の充電量に当たります(0.0~1.0f)
     */
    float BatteryLevelNative() 
    {
        [UIDevice currentDevice].batteryMonitoringEnabled = YES;
        float batteryLevel = [UIDevice currentDevice].batteryLevel;
        
        return batteryLevel;
    }

    /**
     * バッテリーステータスは現在の充電ステータスに当たります
     */
    int BatteryStatusNative() 
    {
        [UIDevice currentDevice].batteryMonitoringEnabled = YES;
        if ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateUnknown) {
            // 不明
            return -1;
        }
        if ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateUnplugged) {
            // 非充電状態
            return 0;
        }
        if ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateCharging) {
            // 充電中状態
            return 1;
        }
        if ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateFull) {
            // フル充電
            return 2;
        }
        return -1;
    }
}

BatteryOverseer.cs

ここはサンプル色が強いです。

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class BatteryOverseer : MonoBehaviour
{
#if UNITY_ANDROID && !UNITY_EDITOR
    // Javaのオブジェクトを作成
    private AndroidJavaClass _overseer;
#elif UNITY_IOS && !UNITY_EDITOR
    [DllImport("__Internal")]
    private static extern float BatteryLevelNative();
    [DllImport("__Internal")]
    private static extern int BatteryStatusNative();
#endif
    [SerializeField]
    private Image _battery;
    [SerializeField]
    private Image _chargeIcon;

    private Coroutine _timer;

    /// <summary>
    /// バッテリーの状態のenumです。
    /// </summary>
    private enum BatteryStatus
    {
        Unplug = 0,
        Charge = 1,
    }

    /// <summary>
    /// コンポーネントの表示時に表示の更新を始めます
    /// </summary>
    private void OnEnable()
    {
        UpdateBattery();
        _timer = StartCoroutine(CoUpdateTimer());
    }

    /// <summary>
    /// コンポーネントの非表示時に表示の更新を止めます
    /// </summary>
    private void OnDisable()
    {
        if (_timer != null)
        {
            StopCoroutine(_timer);
            _timer = null;
        }
    }

    /// <summary>
    /// 1秒に1回状態を更新するためのコルーチンです
    /// </summary>
    private IEnumerator CoUpdateTimer()
    {
        var wfs = new WaitForSeconds(1f);
        while (true)
        {
            yield return wfs;
            UpdateBattery();
        }
    }

    /// <summary>
    /// バッテリーの状態を更新する処理です
    /// </summary>
    private void UpdateBattery()
    {
        float chargeRate = 0f;
        float batteryLevel = 0;
        int batteryScale = 0;
        BatteryStatus batteryStatus = BatteryStatus.Unplug;
#if UNITY_ANDROID && !UNITY_EDITOR
        // Javaのオブジェクトを作成
        if (_overseer == null)
        {
            _overseer = new AndroidJavaClass("com.kayac.uac.BatteryOverseer");
        }

        batteryLevel = _overseer.CallStatic<int>("BatteryLevel");
        batteryScale = _overseer.CallStatic<int>("BatteryScale");
        var androidBatteryStatus = _overseer.CallStatic<int>("BatteryS[f:id:izumikawa-yuichi:20181127183028p:plain]tatus");
        // AC or USB
        if (androidBatteryStatus == 1 || androidBatteryStatus == 2)
        {
            batteryStatus = BatteryStatus.Charge;
        }
#elif UNITY_IOS && !UNITY_EDITOR
        batteryLevel = BatteryLevelNative();
        batteryScale = 1;
        var iOSBatteryStatus = BatteryStatusNative();
        // Charge or Full
        if (iOSBatteryStatus > 0)
        {
            batteryStatus = BatteryStatus.Charge;
        }
#else
        // 端末上の実行でなければゲージはMAX、非接続状態
        batteryLevel = batteryScale = 1;
        batteryStatus = BatteryStatus.Unplug;
#endif
        chargeRate = (float)batteryLevel / (float)batteryScale;
        _battery.fillAmount = chargeRate;
        _chargeIcon.gameObject.SetActive(batteryStatus == BatteryStatus.Charge);
    }
}

Hierarchy

f:id:izumikawa-yuichi:20181127182426p:plain

用意した画像

  Background Gauge ChargeIcon
背景塗りつぶし f:id:izumikawa-yuichi:20181127182433p:plain f:id:izumikawa-yuichi:20181127182437p:plain f:id:izumikawa-yuichi:20181127182440p:plain
実際の画像 f:id:izumikawa-yuichi:20181127182404p:plain f:id:izumikawa-yuichi:20181127182417p:plain f:id:izumikawa-yuichi:20181127182420p:plain

結果

あとはビルドして実行すれば充電中はChargeIconが表示され、
GaugeのFillAmountは充電状態に対応します。

f:id:izumikawa-yuichi:20181127182443p:plain

おわりに

というわけでバッテリー残量と充電状態の取得についてでした。 Unity2018になり組み込みもだいぶ簡単になったので是非トライしてみてください。 アプリ内に組み込んでいただけるとまず私が喜びます。

明日はクアンによるuGUIのマスクの形状をアニメーションさせる話になります。