ともちゃんのアプリ開発日記

組込みC言語プログラマだったともちゃんが、四苦八苦しながら、AndroidのJAVA/Kotlin、iOSのSwiftUIを習得して行きます。ともちゃんの備忘録も兼ねています。

排他制御(synchronizedブロック)

共通資源を排他制御するには、synchronizedブロックを使います。

synchronized(共通資源のインスタンス) {
共通資源へのアクセス
}

 私の実装例は以下です。(Kotlinのサンプルコード)

synchronized(databaseManager.getSwitchingListDatabase()) {
cursor = databaseManager.querySwitchingListDatabase()
}

 

Android 8.0の通知(NotificationChannel)

Android 8.0では、通知にNotificationChannelを設定してやる必要があります。

NotificationManager myNotification = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(DEFAULT_CHANNEL,
resources.getString(R.string.notification_medicine_time),
NotificationManager.IMPORTANCE_DEFAULT);
/* 指定できる値は以下7種類。
NotificationManager.IMPORTANCE_UNSPECIFIED (-1000);
NotificationManager.IMPORTANCE_NONE (0);
NotificationManager.IMPORTANCE_MIN (1);
NotificationManager.IMPORTANCE_LOW (2);
NotificationManager.IMPORTANCE_DEFAULT (3);
NotificationManager.IMPORTANCE_HIGH (4);
NotificationManager.IMPORTANCE_MAX (5);
*/
myNotification.createNotificationChannel(channel);
}
Intent intent = new Intent(context, TakingCheckActivity.class);
intent.putExtra("user", user_id);
intent.putExtra("taking_timing", taking_timing);
intent.putExtra("time_in_millis", time_in_millis);
intent.setType(message);
Intent bootIntent[] = {intent};
PendingIntent contentIntent = PendingIntent.getActivities(context, notification_id, bootIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(context, DEFAULT_CHANNEL);
} else {
builder = new Notification.Builder(context);
}
builder.setSmallIcon(R.drawable.notification_small_icon)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.notification_large_icon))
.setContentTitle(resources.getString(R.string.notification_message_1))
.setContentText(message)
.setWhen(System.currentTimeMillis())
.setPriority(Notification.PRIORITY_DEFAULT)
.setAutoCancel(true)
.setSound(uri)
.setDefaults(Notification.DEFAULT_LIGHTS)
.setContentIntent(contentIntent);

myNotification.notify(notification_id, builder.build());

 

Android 8.0のバックグラウンド処理

Android 8.0では、バックグラウンド処理に厳しい制約が付けられました。

よって、バックグラウンド処理からフォアグラウンド処理に移行するソースコードです。

 

サービスを呼び出す側

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(intent)
} else {
activity.startService(intent)
}

 

サービス側

@TargetApi(Build.VERSION_CODES.O)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(DEFAULT_CHANNEL, getString(R.string.notification_channel), NotificationManager.IMPORTANCE_NONE)
/** 指定できる値は以下7種類。
NotificationManager.IMPORTANCE_UNSPECIFIED (-1000);
NotificationManager.IMPORTANCE_NONE (0);
NotificationManager.IMPORTANCE_MIN (1);
NotificationManager.IMPORTANCE_LOW (2);
NotificationManager.IMPORTANCE_DEFAULT (3);
NotificationManager.IMPORTANCE_HIGH (4);
NotificationManager.IMPORTANCE_MAX (5);
**/
notificationManager.createNotificationChannel(channel)

}

val intent = Intent(applicationContext, MainActivity::class.java)
val intents = arrayOf(intent)
val pendingIntent = PendingIntent.getActivities(applicationContext, 0, intents, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = NotificationCompat.Builder(applicationContext, DEFAULT_CHANNEL)
builder.setContentIntent(pendingIntent)
builder.setContentTitle(resources.getString(R.string.app_name))
builder.setContentText(resources.getString(R.string.please_tap_here))
builder.setWhen(System.currentTimeMillis())
builder.setAutoCancel(false)
builder.setSmallIcon(R.drawable.notification_small)
builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.notification_large))

val notification = builder.build()
notification.flags = Notification.FLAG_NO_CLEAR

startForeground(1, notification)

startForegroundService()でサービスを起動してから、5秒以内にstartForeground()を呼び出す必要があります。

 

端末がスリープしても動き続けるバックグラウンド処理

HandlerとWakeLockで実現しました。(ソースコードはKotlinで書いています。)

 

AndroidManifest.xml

<uses-permission android:name="android.permission.WAKE_LOCK" />

 

 時間を刻むタイマーサービス

TimerService.kt

class TimerService : Service() {

val handler = Handler()

companion object {
private var wakelock : PowerManager.WakeLock? = null
private var timer : Timer? = null
}

override fun onBind(intent: Intent): IBinder? {
return null
}

override fun onCreate() {
super.onCreate()

val databaseManager = DatabaseManager()
databaseManager.openDatabaseAll(applicationContext)

val myNotification = MyNotification(applicationContext)
myNotification.displayNotification()

val powerManager = applicationContext!!.getSystemService(Context.POWER_SERVICE) as PowerManager
wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PartialWakelock")
wakelock!!.acquire()

}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)

var count = 0L
var timePrevious : Long
var timeNow = System.currentTimeMillis()
var timeDifference : Long

handler.postDelayed(object : Runnable {
override fun run() {
OnOffSetter() // タイマー内の処理
handler.postDelayed(this, 15*1000)

timePrevious = timeNow
timeNow = System.currentTimeMillis()
timeDifference = timeNow - timePrevious
count++
Log.i("TimerService", count.toString()+"/"+timeDifference.toString())

}
}, 15*1000)

return START_STICKY
}

override fun onDestroy() {
super.onDestroy()

handler.removeCallbacksAndMessages(null)

val myNotification = MyNotification(applicationContext)
myNotification.cancelNotification()

if (wakelock != null) {
wakelock!!.release()
}

}
}

 

 タイマーを起動する処理

val intent = Intent(this, TimerService::class.java)
startService(intent)

stopService(intent)

 

 

WakeLockは、バッテリーを消費するので、必要最低限にする必要があります。

 

AlarmManagerを指定した時間に発火させる

Android 6.0以降、Dozeモードが導入されて、AlarmManagerも影響を受けるようになりました。

AlarmManagerを指定した時間に発火させるには、

  • Android 6.0(Marshmallow)以上:AlarmManger#setExactAndAllowWhileIdle()
  • Android 4.4(KitKat)以上:AlarmManger#setExact()
  • その他:AlarmManger#set()

を使い分ける必要があります。

AlarmManager alarmRegister = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmRegister.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmStartTime, alarmIntentRegister);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmRegister.setExact(AlarmManager.RTC_WAKEUP, alarmStartTime, alarmIntentRegister);
} else {
alarmRegister.set(AlarmManager.RTC_WAKEUP, alarmStartTime, alarmIntentRegister);
}

なお、AlarmManger#setExactAndAllowWhileIdle()には、

  1. 発火時の処理を10秒以内にする。
  2. 最短発火頻度は15分に1回

という制約があるそうです。

 

Androidアプリの課金テストのアカウント

課金テスト用のアカウントは、Google Play Consoleの

[設定]→

[テスタの管理]

だけでなく、

[設定]→

[アカウントの詳細]→

[テスト用のアクセス権がある Gmail アカウント]

にも登録しないと、課金されてしまいます。

Android 6.0のRuntime Permission

Android 6.0のRuntime Permissionの取得方法です。

 

1.Activityで実行する例

ストレージのアクセス権を得る場合の例です。

if (ContextCompat.checkSelfPermission(ExportImportActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// アクセス権がすでに付与されているときの処理
} else {
// アクセス権を付与する処理
ActivityCompat.requestPermissions(ExportImportActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}

 

  ユーザが許可/不許可を選択したときの処理

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case 0: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//許可された場合の処理
}
break;
}
}
}

 

2.Fargmentで実行する例

ストレージのアクセス権を得る場合の例です。

    if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// アクセス権がすでに付与されているときの処理
} else {
// アクセス権を付与する処理
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag("ExportImport");
FragmentCompat.requestPermissions(fragment, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
} else {
// Android 6.0未満の時
}

 

 ユーザが許可/不許可を選択したときの処理

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case 0: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//許可された場合の処理
}
break;
}
}
}