2020年7月30日木曜日

Android10 充電ケーブル 抜き差しのイベント来ない?

充電ケーブルの抜き差しのイベントを検知するアプリを作っているのですが、
Android10でテストしたところ、うまく動作しないことがわかりました。
(イベントを検知するやり方を記載した記事はこちらです。)

プログラムは、公式の情報を参考にしているので、間違えはなさそう。
他のOSのバージョンもテストしたところ、Android8, Android9は問題なし。

ちなみにテストはemulatorで実施しており、emulatorの電源の状態を変更するコントロールでAC charger / noneを切り替えて実施してます。

環境

Android Studio : 
スマホ : emulator Pixel 3a API29 (android10)

これが原因か?①

emulatorを起動すると、常にUSB充電中となっていることがわかりました。

これについては、開発モードにはいって(ビルド番号を何回か連打)、USBデバックをOFFにすると解除されることが分かりました。

→ USB充電中は解除されたが、電源のイベントを拾えないのは変わらず。

※Android9もUSBデバックはONだったので、emulatorはONになっているのかもしれません。


これが原因か?②

emulator (もしくはOS)が悪そう。
emulatorのバッテリーのコントロールパネルで電源の状態を変更しても、
emulator側に反映されていないことが分かりました。

左がemulator, 右がコントロールパネルです。
levelやconnectionがあっていないことが分かります。



これが原因か?③

②の原因ですが、Android10で「電池使用料のトラッキング」について変更が入ってます。
これが原因かどうかはもうちょっと情報を集めてみようと思います。

以下引用です。
------------------------
Android 10 以降では、主要な充電イベントの後にデバイスのプラグを抜くと、SystemHealthManager によって電池使用量の統計情報がリセットされます。主要な充電イベントとはつまり、デバイスが完全に充電されること、またはほとんど電池残量がない状態からほぼ完全に充電された状態になることです。

Android 10 より前は、電池残量がほとんど変化していなくても、デバイスのプラグを抜くと電池使用量の統計情報がリセットされていました。
------------------------


結論

すみません。まだよくわかってません。





2020年7月23日木曜日

Android Foreground Serviceのメモ

やりたいこと
ボタンを押すと、フォアグラウンドサービスを起動するやり方を記録しておきます。

環境
OS : Windwos10
Android Studio 4.0
言語 : java

参考にしたサイト
■Foreground Serviceの基本
https://qiita.com/naoi/items/03e76d10948fe0d45597
おおむねこちらのページを参考にしてます。

■公式情報
https://developer.android.com/guide/components/services?hl=ja
公式情報も参考にしました。

背景
※ここは読み飛ばしていただいて結構です。※
充電ケーブルが接続されたかどうかを検知して、何か処理するというアプリを検討中なのですが、通常のアプリだと裏側にいると勝手にOSによって終了させられてしまいます。

Androidの公式ページでこのように記載されてます。
「システムは RAM を解放する必要がある場合にプロセスを強制終了します。システムが特定のプロセスを強制終了する可能性は、...」
https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ja

また、Android7まではマニフェストに「イベントを検知するよ。」という一文を書いておくと、アプリのプロセスがいなくても、イベントが発生すると再起動してくれたのですが、Android8以降はアプリのプロセスがいないと、イベントがひろえなくなってしまいました。
(イベントを検知する仕組みの詳細はこちらを参考にしてください。)

じゃあどうするかというと、アプリをServiceとして裏側で生かしておいて、常にイベントを検知できるようすればよいということになります。

それで終わりかというと、Android8以降ではまたルール改正が発生しており、Serviceをバックグラウンド(通知とかなにもださずに)で動作させていると、強制終了させられるというルールが追加されました。
詳しくはこちらを参考にしてください。


ルール改正がはげしい...


じゃあどうするかというと、その答えがForegroundServiceとなります。

ということで、前置きが長くなりましたがForegroundServiceの勉強をしたので、メモしておきます。
ざっくり言うと
ざっくりいうと以下の手順となります。
・serviceクラス作成
   この中で自分がForegroundであることを知らせるための処理をごちゃごちゃいれる。
・通知用のアイコン準備 ・Manifestにserviceクラスを登録
・MainActivityからserviceを起動

それでは、細かく書いていきます。

Step1.準備
1.Android StudioでEmpty Activityでプロジェクトを作成する。
Name : MyApp
Package name : test.test ← 適当につけました。
Minimum SDK : Api 16 (android 4.1) ←デフォルトのまま

2.ボタンを1個配置して、OnClickListenerを作る。
後でこのボタンを押すとServiceが起動するようにします。
大事なところはまだですが、ここまではこんな感じのコードになります。
package test.test;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ボタンのイベント
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // サービスを呼ぶ
            }
        });
    }
}
Step2.通知用のアイコン作成
serviceクラスから通知(notification)を行う必要があるのですが、通知用の小さいアイコンが必須になるので、事前に作っておきます。

(1)app - res - drawable - 右クリック - New - Image Assetを選択

(2)下図の①のところをNotification Iconsに設定、
    ②のところに任意の名称を設定(ここではic_service_notificationに設定しました)、
    ③でiconの形状を設定(ここではデフォルトのままにしました。)します。

  (3)設定したらNext - Finish
Step3.serviceクラス作成
package test.test;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;


//(1) Serviceの派生クラスを作成
public class MyService  extends Service {

    //(2) 必須のメソッド。 バインドしない場合はnullを返す。
    @Override
    public IBinder onBind(Intent intent){
        return null;
    }

    //(3.3)の一部  startforegroudnに渡すIDを定義する。 0以外の数字 。
    private static final int ONGOING_NOTIFICATION_ID = 1;

    //(3) 呼び出し側でstartService()かstartForegroundService() をすると呼び出されます。
    @Override
    public int onStartCommand(Intent intent,
                              int flags,
                              int startId) {

        //(3.1)  チャンネルIDの登録
        String channelID = "MY_CHANNEL_ID"; // 通知チャンネル用のID。適当な名前を定義 
        createNotificationChannel(channelID);

        // 通知をタップしたときにアプリを呼び出す準備
        Intent notificationIntent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent =  PendingIntent.getActivity(this, 0, notificationIntent, 0);

        // 通知の準備
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelID)
                .setContentTitle("通知のタイトル")
                .setContentText("通知の内容")
                .setSmallIcon(R.drawable.ic_service_notification)
                .setContentIntent(pendingIntent)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT);

        // (3.3) このAPIを呼ぶと、フォアグラウンドサービスとなる。
        startForeground(ONGOING_NOTIFICATION_ID, builder.build());

        // (3.4) 戻り値を指定。
        return START_STICKY;
    }


    // 通知のチャンネルを作成する。この関数はほぼもってきたそのまま。
    // https://developer.android.com/training/notify-user/build-notification?hl=ja
    private void createNotificationChannel(String channelID) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            //アプリ - 通知 に表示される情報
            CharSequence name = "サービス起動中の通知"; // なんでもOK。
            String description = "サービス起動中の通知は、XXXのための通知です。"; // なんでもOK。

            // 通知のレベルとか名称とか設定
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(channelID, name, importance);
            channel.setDescription(description);

            // 通知の登録
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }
}

上記コードの説明
(1)serviceクラスを継承したクラスを作成します。
(2)onBind()を実装。必須のメソッドとなります。
  bindしない場合はreturn nullでOKです。
(3)onStartCommand()を実装。
  親アプリがstartService()をするとこちらが呼ばれます。
(3.1) createNotificationChannelをして、通知チャンネルを作成。Android8以降で必要。
    設定 - 通知で使用される機能になります。
(3.2) 通知の準備
フォアグラウンドサービスにするには、通知を表示して、生きていることをユーザーに知らせる必要があるため、必須となります。 その準備として、通知のタイトルや通知のアイコンなどを準備してます。

 (3.3) startForeground()を実行
このAPIを呼び出すことでフォアグラウンドサービスとなります。第一引数のidには0以外 の値を設定する必要があります。ここでは1を指定しました。

(3.4) 戻り値を設定( 3種類あるので用途に合わせて設定 )

このコードでは文字列は直値を記載してますが、実際はstrings.xmlに定義して、使用したほうが良いと思います。

Step4.manifest
(1) applicationの中に、<service android:name=".MyService">を追加します。
(2) application>の外に、<uses-permission android:name="android.permission.FOREGROUND_SERVICE">を追加します。

(2)のほうは、Android9以降では必須となります。

以下xml全体となります。うまくコードをはれなかったので、図を張ってます。

Step5.MainActivityからサービスを呼ぶ
package test.test;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ボタンのイベント
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // サービスを呼ぶ
                Intent serviceIntent = new Intent(MainActivity.this, MyService.class);
                startService(serviceIntent);
            }
        });
    }
}
Step5.テスト
emulatorの5.1と10で動くことだけ確認しました。

2020年7月10日金曜日

最近勉強したことのメモ ( Android )

SQLite
こちらを参考に実装しました。
大変参考になりました。
https://qiita.com/kengo_kuwahara/items/a8ef858a9810cad42ca6
Fragment
Fragmentがいくつかあって、それぞれから共通の処理を呼ぶときどうするのかな?
というのがよくわかりませんでした。

DBのアクセスを複数のFragmentからアクセスしたかったためです。

あってるのかどうかは不明ですが、以下のように実装しました。

1.MainActivityに共通の処理を記載する(SQLのHelperクラスを持つ)
2.MainActivityにDBを読むメソッドを追加する。
3.FragmentでgetActivity()をしてMainactivityを取得してMainActivityのメソッドを使う。
ConstraintLayout
なんか思ったように動かないんですが、
効率もよいとの情報もあり、BasicActivityを選択するとこれでUIが作られるので、これを使ってます。

・コネクタでUIの要素どうしをつなぐ。
・余白と、layout_widh, layout_heightで微調整する。
これをAndroid StudioのAtrributeで調整したほうがよい。
(指定できないものとかもあるので)
・たとえばmach_parentは使えないようだが、xmlとしてはかけてしまう。