端末がスリープしても動き続けるバックグラウンド処理
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()には、
- 発火時の処理を10秒以内にする。
- 最短発火頻度は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;
}
}
}
AChartEngineで円グラフを描く
AChartEngineで円グラフを描く
public void displayPieChart(
int dataCount,
String label[],
int data[],
int kind
) {
Resources resources = context.getResources();
if (dataCount == 0) {
chartLayout.removeAllViews();
return;
}
CategorySeries series = new CategorySeries(null);
String actualLabel = "";
for (int i=0; i<dataCount; i++) {
actualLabel = label[i] + "(" + String.valueOf(data[i]) + resources.getString(R.string.yen) + ")";
series.add(actualLabel, data[i]);
}
DefaultRenderer renderer = new DefaultRenderer();
renderer.setShowLabels(false); //ラベルを表示するか
// renderer.setLabelsTextSize(15); //ラベルの文字サイズ
renderer.setShowLegend(true); //凡例を表示するか
renderer.setLegendTextSize(24); //凡例の文字サイズ
//データの数字を表示する or しないを指定。デフォルトは表示しない
renderer.setDisplayValues(false);
//スタートの角度を指定。デフォルトでは、3時の方向。12時の方向からスタートの場合は270を指定。
renderer.setStartAngle(270);
// スクロール禁止
renderer.setPanEnabled(false);
// ズーム禁止
renderer.setZoomEnabled(false);
String[] colors = {
"#F44336", // Red
"#E91E63", // Pink
"#9C27B0", // Purple
"#673AB7", // Deep Purple
"#3F51B5", // Indigo
"#2196F3", // Blue
"#03A9F4", // Light Blue
"#00BCD4", // Cyan
"#009688", // Teal
"#4CAF50", // Green
"#AED581", // Light Green
"#CDDC39", // Lime
"#FFEB3B", // Yellow
"#FFC107", // Amber
"#FF9800", // Orange
"#FF5722", // Deep Orange
"#795548", // Brown
"#9E9E9E", // Gray
"#607D8B", // Blue Gray
};
DatabaseManager databaseManager = new DatabaseManager();
for (int i=0; i<dataCount; i++) {
SimpleSeriesRenderer seriesRenderer = new SimpleSeriesRenderer();
if (kind == databaseManager.DB_KIND_SPENDING) {
seriesRenderer.setColor(Color.parseColor(colors[i % colors.length]));
} else {
seriesRenderer.setColor(Color.parseColor(colors[(colors.length-1)-(i % colors.length)]));
}
renderer.addSeriesRenderer(seriesRenderer);
}
GraphicalView mChartView = ChartFactory.getPieChartView(context, series, renderer);
chartLayout.removeAllViews();
chartLayout.addView(mChartView);
}
Google Playでapkの公開に時間がかかった
Google Playでアプリの公開に時間がかかりました。
36時間経っても公開されず、さすがにおかしいと思い、バージョンコードを一つ上げて、apkをアップデートしました。
そうすると、何事も無かったかのように、2時間後くらいに新バージョンコードのapkが公開されました。
端末起動時の時間のかかる処理
端末起動時に処理を動かすには、
android.intent.action.BOOT_COMPLETED
をキャッチしてやります。
public class BootBroadcastReceiver extends BroadcastReceiver {
Context context;
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
DataCenter dataCenter = new DataCenter();
dataCenter.setContextNotificationScheduleOnBoot(context);
Intent notificationScheduleOnBoot = new Intent(context, NotificationScheduleOnBoot.class);
context.startService(notificationScheduleOnBoot);
}
}
}
ただ、BroadcastReceiverの処理は、短い処理でないといけないので、処理が長くなる時は、IntentServiceを使用します。
public class NotificationScheduleOnBoot extends IntentService {
public NotificationScheduleOnBoot() {
super("NotificationScheduleOnBoot");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
// 長くかかる処理
}
}
}