Androidアプリ開発

バックアップ(zip圧縮)の実装

この記事は約23分で読めます。
記事内に広告が含まれています。
スポンサーリンク

こんにちは、まっさん(@Tera_Msaki)です。

この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaでの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、記事の最後で紹介します)

冷たい飲み物も真空断熱マグカップだと、しばらく冷たいままです

◎テーマ
複数あるファイルをzip圧縮してバックアップファイルを作成する

◎ポイント

複数あるファイルを1ファイルにまとめることで、ファイルのハンドリングが楽になります。
パッケージ(java.util.zip)を使用すれば、アプリで使用しているデータファイルをZip圧縮して、1ファイルにまとめることが可能です。

◎外部ストレージに対する権限とアクセス

Android11(APIレベル30)以降では、外部ストレージ上のアプリ固有のディレクトリの外にあるファイルにアクセスできなくなりました。
外部連携時の出力先フォルダとして、ダウンロードを使用する場合は、SAF(Storage Access Framework)を使用します。

◎Java 制御部分のコーディング(MainActivity.java)

	:
    private final Handler               handler = new Handler(Looper.getMainLooper());
	:
    //バックアップ
    private final ActivityResultLauncher<Intent> activityResultLauncher1 = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    if (result.getData() != null) {
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                handler.post(() -> getArchiveDatabase().dbExport(context, result.getData().getData()));
                            }
                        }).start();
                    }
                }
            });
	:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context = getApplicationContext();
        setMainActivity(this);
        setArchiveDatabaseHelper(new ArchiveDatabaseHelper(context));
	:
    }
    @Override
    protected void onResume() {
        super.onResume();
        context = getApplicationContext();
	:
        //upload ボタン(バックアップ)
        upload = findViewById(R.id.upload);
        upload.setOnClickListener(v -> {
            Intent intentS = new Intent(Intent.ACTION_CREATE_DOCUMENT);
            intentS.addCategory(Intent.CATEGORY_OPENABLE);
            intentS.setType("application/zip");
            intentS.putExtra(DocumentsContract.EXTRA_INITIAL_URI, String.format("%s.zip",context.getString(R.string.app_name)));
            activityResultLauncher1.launch(intentS);
        });
	:
    //データエクスポート//
    public void dataExport(List<String> fileNames, Uri uri) {
        ExternalStorageWriter externalStorageWriter = new ExternalStorageWriter(context);
        externalStorageWriter.compress(fileNames, context, uri);
    }
    //データエクスポート完了//
    public void dataExportComplete() {
        toastMessage(R.string.data_export);
    }

バックアップボタンのクリックで、Intentを設定して、SAFを呼び出します。
onActivityResultが非推奨で使用できなくなったので、activityResultLauncherを使用します。
SAFからの応答より、バックアップファイルのURIを取得します。

registerForActivityResultで取得したバックアップファイルのURIを引数として、バックアップ処理を起動します。
バックアップ処理は時間がかかるため、UIスレッドでタイムアウトを回避する実装が必要です。

バックアップ処理では、データベースにあるデータをテーブル単位でファイル出力します。
出力したファイルの一覧を引数として、zip圧縮処理を起動します。
zip圧縮処理の完了後にバックアップ終了メッセージをトースト表示します。

◎Java バックアップ処理のコーディング(ArchiveDatabaseHelper.java)

	:
public class ArchiveDatabaseHelper extends ArchiveUtilities {
    private static final boolean    DEBUG = false;
    private static final String     TAG = ArchiveDatabaseHelper.class.getSimpleName();
	:
    public void dbExport(Context context, Uri uri) {
        List<String> fileNames = new ArrayList<>();
        ExternalStorageWriter externalStorageWriter = new ExternalStorageWriter(context);
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> {
            try {
                //SELECT ALL
	:
                partsList = partsDao.select();
	:
            } catch (Exception e) {
                Log.d(TAG, String.format("dbExport:%s", e.getMessage()));
                e.printStackTrace();
            }
            handler.post(() -> {
	:
                tsvFile = String.format("%s.tsv",Parts.class.getSimpleName());
                if (externalStorageWriter.initializeFile(tsvFile, 0)) {
                    for (Parts parts : partsList) {
                        externalStorageWriter.writeFile(String.format(Locale.JAPAN,"%d\t%s \t%s \t%d\n",
                                parts.getPart(), parts.getItem(), parts.getMemo(), parts.getUsage()), true);
                    }
                    fileNames.add(tsvFile);
                }
	:
                getMainActivity().dataExport(fileNames, uri);
            });
        });
    }

データベースにあるテーブルに対して、SQLを発行してデータをファイルに出力するとともにファイル一覧を作成します。
データファイルの出力後、UIスレッド経由でZip圧縮処理を起動します。

◎Java ユーティリティクラスのコーディング(ArchiveUtilities.java)

public class ArchiveUtilities extends AppCompatActivity {
    private static MainActivity           mainActivity = null;
    private static ArchiveDatabaseHelper  archiveDatabaseHelper = null;
	:
    //コンテキスト
    public void setContext(Context newContext) {
        if (context == null) {
            context = newContext;
        } else if (!context.equals(newContext)) {
            context = newContext;
        }
    }
	:
    //アクティビティ//
    public MainActivity getMainActivity() {
        return mainActivity;
    }
    public void setMainActivity(MainActivity newActivity) {
        if (maiActivity == null) {
            mainActivity = newActivity;
        } else if (!mainActivity.equals(newActivity)) {
            mainActivity = newActivity;
        }
	:
    //ArchiveDatabaseHelper//
    public ArchiveDatabaseHelper getArchiveDatabase() {
        return archiveDatabaseHelper;
    }
    public void setArchiveDatabaseHelper(ArchiveDatabaseHelper newArchiveDatabaseHelper) {
        if (archiveDatabaseHelper == null) {
            archiveDatabaseHelper = newArchiveDatabaseHelper;
        }
    }
    //トースト表示//
    public void toastMessage(int message) {
        Toast toast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
        toast.show();
    }

ユーティリティクラスは、インスタンス化したアクティビティとデータベース操作クラスをハンドリングするクラスです。

◎Java Zip圧縮処理のコーディング(ExternalStorageWriter.java)

	:
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ExternalStorageWriter extends ArchiveUtilities {
    private static final boolean    DEBUG = false;
    private static final String     TAG = ExternalStorageWriter.class.getSimpleName();
    private final String[]          type = new String[] {
            Environment.DIRECTORY_DOCUMENTS,
            Environment.DIRECTORY_DOWNLOADS,
            Environment.DIRECTORY_PICTURES,
    };
    private final File[]            path = new File[type.length];
    private File                    file;
    private final File              propertiesFile;
    private final Handler           handler = new Handler(Looper.getMainLooper());
    private InputStream             inputStream;
    private ZipOutputStream         zipOutputStream = null;
    private File                    zipFile;
    private final byte[]            buffer = new byte[1024];

    //コンストラクタ
    public ExternalStorageWriter(Context context) {
        for (int i=0; i< type.length; i++) {
            path[i] = context.getExternalFilesDir(type[i]);
            if (DEBUG) Log.d(TAG, String.valueOf(path[i]));
        }
        propertiesFile = new File(context.getFilesDir(),String.format("%s.properties", context.getString(R.string.app_name)));
    }
    //ファイル初期化
    public boolean initializeFile(String fileName,int type) {
        if (DEBUG) Log.d(TAG, String.format("initializeFile:%s:%d",fileName,type));
        file = new File(path[type], fileName);
        return (!file.exists() || file.delete());
    }
    //ファイル書込(text)
    public void writeFile(String data, boolean mode) {
        if (isExternalStorageWritable()) {
            if (DEBUG) Log.d(TAG, String.format("writeFile:%s:%s",data,mode));
            try (FileOutputStream fileOutputStream = new FileOutputStream(file, mode);
                OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(outputStreamWriter)) {
                bw.write(data);
                bw.flush();
            } catch (Exception