S3のオブジェクトをAWS CLIを利用して一括でダウンロードする

表題の通りです。

【 AWS 公式 ドキュメント】 オブジェクトのダウンロード

S3コンソールの使用のところの注記に「一度にダウンロードできるオブジェクトは 1 つだけです。」とあります(2023/02/17時点)。

ディレクトリをダウンロードしようとチェックを入れても、ダウンロードの部分が有効になりません。

コンソールでやろうとするのは諦め、aws-cliを利用して行いました。

aws-cliのバージョンは、2.2.38です。

上の画像にある「testdir」をダウンロードしたいと思います。
コンソールでダウンロードしたいオブジェクトをクリックして、「S3 URIをコピー」しておきます。

注意点ですが、 公式ドキュメントにあるように

オブジェクトをダウンロードすると、データ転送料金が適用される

ので、不必要に大きな容量をダウンロードするのはやめた方がいいと思います。

実行コマンドは下記です。

aws s3 cp コピーしたURI コピーしたいローカルPCのパス --recursive

自分は、ホームディレクトリにあるドキュメントのtestディレクトリに入れたかったので以下のコマンドでダウンロードしました。

【Java】Gsonオブジェクトが、toJsonメソッドを呼び出した際に値がnullのものは、消失してしまう

タイトルだけだとどういうことかわからないので、サンプルを書きます。

目標はGsonを使って以下のようなJsonを生成することです。

{"hoge":"hoge","fuga":null}

Gsonを使ってJsonを生成するのは慣れていた(つもり)だったので以下のようにサラサラと書いてみます。

Map<String, String> map = new HashMap<>();
map.put("hoge", "hoge");
map.put("fuga", null);

Gson gson = new Gson();
String json = gson.toJson(map);

jsonの中身を見ると…

{"hoge":"hoge"}

俺のfugaが消えています…

調べてみると、GsonにはBuilderが用意されてて、そこでserializeNulls()を呼んで create()すると良いみたいです。

Map<String, String> map = new HashMap<>();
map.put("hoge", "hoge");
map.put("fuga", null);
GsonBuilder gsonBuilder = new GsonBuilder().serializeNulls();
Gson gson = gsonBuilder.create();
String json = gson.toJson(map);

結果

{"hoge":"hoge","fuga":null}

fugaを取り戻しました。

serializeNulls()の中身を見ると、GsonBuilderのserializeNullsプロパティがtrueにするような処理が走っていました。
serializeNullsプロパティはデフォルトではfalseになっていました。

そもそも GsonBuilder なるものがあるのを知らなかったですね…勉強になりました。

<参考サイト>

【Quita 】Gsonはデフォルトではnullなフィールドをシリアライズしない

【Java】JSONObjectで、getIntやoptIntで null の場合は0にせず null のままでいてほしい

int 型 はnullにならないので、しょうがないのですが、optIntegerのようなものが欲しいわけです。
本件、タイトルのことを上司に聞いたら即、一番下にある参考サイトを見つけてくれました。
情報収集力に雲泥の差がありますね…。

JSONObjectにそんなものがなかったのでなければ、作りましょうというのが今回のお話です。
ついでにDouble型も作ります。

public class JSONUtil {

    public static Integer optInteger(JSONObject jsonObject, String key) throws JSONException {
        return (jsonObject.isNull(key)) ? null : jsonObject.getInt(key);
    }

    public static Double optWrapperDouble(JSONObject jsonObject, String key) throws JSONException {
        return (jsonObject.isNull(key) || Double.isNaN(jsonObject.getDouble(key))) ? null : jsonObject.getDouble(key);
    }

}

0 とか 入力なしをきちんとわけたいときってどうするのがいいのかいまだに迷います。
Stringは、””でいい気はしますが…。
個人的には、nullは、 すぐNullPointerExceptionにはまるため使いたくないので…。

<参考サイト>

【Stack Overflow】Json Parsing and Nullable int value in android

【Java】Caller パッケージ名 needs to hold android.permission.SCHEDULE_EXACT_ALARM to set exact alarms.

タイトルのエラーに出くわしてしまいました。

AndroidManifest.xml内 で以下のように宣言するだけで良いと思っていたので出くわす理由がわかりませんでした…

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

公式ドキュメント

AlarmManager を使って正確な時間にアラームを設定してサービスを動かしたかったのでこれで、AlarmManager の setExact() などのexactを含むメソッドを利用できると思っていました。

てか、実際にそう出来る端末がありました。
ただ、他にandroid でタイトルのエラーを起こす機種がいました。

何はともあれ対策です。

どうやら AlarmManager には 正確にスケジューリング出来るか確認できるメソッド canScheduleExactAlarms() があるようです。

android12以上だと使えます。

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

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
    alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
} else {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
}

canScheduleExactAlarms() メソッドのドキュメントを見ると

SCHEDULE_EXACT_ALARM権限を持つ、またはデバイスの省電力除外リストに入っているとtrueを返してくれるみたいです。

つまりエラーが起きたということはどちらともダメだったということです。
上述した実際に出来た端末は元々デバイスの省電力除外リストに入れていたからっぽいですね…

なんにせよユーザー側で設定を変えれるので上記のような分岐は行う必要があります。

Exactを使わせたい場合は、Intentで「Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM 」を指定して設定してもらうか省電力モードから外す必要がありそうです。

12への変更点に書いておいて欲しかったです…

【Java】Fatal Error Context.startForegroundService() did not then call Service.startForeground()

凄く大事なことなので、載せておきます。
Android8.0対応をした方には凄く悩まされた記事だと思いますが…

公式ドキュメント

中盤辺りに以下のように書かれています。

Android 8.0 では、フォアグラウンドで新しいサービスを開始する startForegroundService() メソッドが新たに導入されています。 システムによってサービスが作成されると、アプリは、サービスの startForeground() メソッドを 5 秒以内に呼び出して、その新しいサービスの通知をユーザーに表示します。 アプリが startForeground() を制限時間内に呼び出さない場合、システムによってサービスが停止され、アプリが ANR になります。

要は、startForegroundService() を呼んで、サービスを起動したときに startForeground() メソッドを 5 秒以内に呼び出さないとクラッシュしてしまいます。

intent = new Intent(context, SampleService.class);
context.startForegroundService(intent);

SampleService.java

@Override
public int onStartCommand(Intent intent, int flags, int startid) {
    super.onStartCommand(intent, flags, startid);
    boolean hasPermission = false;

    // hasPermissionがない場合、クラッシュする
    if (hasPermission) {
        NotificationCompat.Builder builder = buildNotification(); // 自作の通知作成メソッド
        startForeground(NOTIFICATION_ID, builder.build()); // NOTIFICATION_IDは任意の数字
    }
}

上記の例だとクラッシュしてしまいます。
startForeground() が必ず呼ばれる仕組みになっているか確認が必要です。
serviceの中身が複雑になってくればくるほど、確認がしづらいのでシンプルな設計を心掛けたいですね。
IDEがこの辺り検知してくれないかな…