Androidアプリ開発

リストア(zip展開)の実装

この記事は約21分で読めます。
スポンサーリンク

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

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

◎テーマ
Zip圧縮しているバックアップファイル(リストアで使用するファイル)を展開する

◎ポイント

zip圧縮しているファイルを展開できると、
リストア処理や外部連携でのファイルのハンドリングが楽になります。
パッケージ(java.util.zip)を使用すれば、
zip圧縮しているファイルを展開して、
圧縮前のファイルに戻すことが可能です。

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

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

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

	:
    private final Handler               handler = new Handler(Looper.getMainLooper());
	:
    //リストア
    private final ActivityResultLauncher<Intent> activityResultLauncher2 = 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(() -> {
                                    ExternalStorageReader externalStorageReader = new ExternalStorageReader(context);
                                    externalStorageReader.extract(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();
	:
        //download ボタン(リストア)
        download = findViewById(R.id.download);
        download.setOnClickListener(v -> {
            if (DEBUG) Log.d(TAG, "download:onClick");
            Intent intentS = new Intent(Intent.ACTION_OPEN_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)));
            activityResultLauncher2.launch(intentS);
        });
	:
    //データインポート完了//
    public void dataImport() {
        toastMessage(R.string.data_import);
    }

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

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

リストア処理では、リストアするファイルのURIを引数としてzip展開処理を起動します。
zip展開処理では、zip圧縮しているファイルを展開して、データベース更新処理(リストア)を起動します。
データベース更新処理(リストア)の完了後にリストアの終了メッセージをトースト表示します。

◎Java zip展開処理のコーディング(ExternalStorageReader.java)

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

public class ExternalStorageReader extends ArchiveUtilities {
    private static final boolean    DEBUG = false;
    private static final String     TAG = ExternalStorageReader.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 Context           context;
    private final File              propertiesFile;
    private final Handler           handler = new Handler(Looper.getMainLooper());
    private ZipInputStream          zipInputStream;
    private BufferedOutputStream    bufferedOutputStream;
    private ZipEntry                zipEntry;
    private File                    zipFile;
    private int                     len;

    //コンストラクタ
    public ExternalStorageReader(Context context) {
        this.context = context;
        for (int i=0; i< type.length; i++) {
            path[i] = context.getExternalFilesDir(type[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());
    }
	:
    //テキストファイル読込
    public ArrayList<String> ReadFileString() {
        ArrayList<String>       stringList = new ArrayList<>();
        //現在ストレージが読出しできるかチェック
        if (isExternalStorageReadable()) {
            try (FileInputStream fileInputStream = new FileInputStream(file);
                InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
                BufferedReader reader= new BufferedReader(inputStreamReader) ) {
                String lineBuffer;
                while( (lineBuffer = reader.readLine()) != null ) {
                    stringList.add(lineBuffer);
                }
            } catch (Exception e) {
                Log.d(TAG, Objects.requireNonNull(e.getMessage()));
                e.printStackTrace();
            }
        }
        return stringList;
    }
	:
    //ZIP展開
    public void extract(Context context, Uri uri) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> {
            try {
                zipInputStream = new ZipInputStream(
                        context.getContentResolver().openInputStream(uri));
                while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                    if (zipEntry.getName().equals(propertiesFile.getName())) {
                        zipFile = propertiesFile;
                    } else {
                        zipFile = zipEntry.getName().substring(zipEntry.getName().lastIndexOf(".")).equals(".jpeg") ?
                                new File(path[2], zipEntry.getName()) :
                                new File(path[0], zipEntry.getName());
                    }
                    bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(zipFile));
                    byte[] buffer = new byte[1024];
                    while ((len = zipInputStream.read(buffer)) != -1) {
                        bufferedOutputStream.write(buffer, 0, len);
                    }
                    zipInputStream.closeEntry();
                    bufferedOutputStream.close();
                }
            } catch (IOException e) {
                Log.d(TAG, Objects.requireNonNull(e.getMessage()));
                e.printStackTrace();
            }
            handler.post(() -> getArchiveDatabase().dbImport(context));
        });
    }

対象範囲別ストレージのため、zip展開したファイルは拡張子で画像とそれ以外を判断し、
ファイルの格納先フォルダを切り替えながら、ファイル出力します。
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 データベース更新処理(リストア)のコーディング(ArchiveDatabaseHelper.java)

	:
    //インポート//
    public void dbImport(Context context) {
        List<String> remains = new ArrayList<>();
        ExternalStorageReader externalStorageReader = new ExternalStorageReader(context);
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> {
            try {
                String tsvFile;
	:
                //IMPORT(Parts)
                tsvFile = String.format("%s.tsv",Parts.class.getSimpleName());
                if (externalStorageReader.setFile(tsvFile, 0)) {
                    //TRUNCATE
                    partsDao.delete();
                    //INSERT
                    ArrayList<String> tsvData = externalStorageReader.ReadFileString();
                    for (String data : tsvData) {
                        String[] values = data.split("\t");
                        Parts parts = new Parts(
                                Integer.parseInt(values[0]), values[1].trim(), values[2].trim(), Integer.parseInt(values[3]));
                        partsDao.insert(parts);
                    }
                }
	:
            } catch (Exception e) {
                Log.d(TAG, String.format("dbImport:%s", e.getMessage()));
                e.printStackTrace();
            }
            handler.post(() -> {
                getMainActivity().dataImport();
            });
        });
    }

zip展開後のファイルを入力データとして、
入力データ毎にSQLを発行してデータベースを更新(リストア)します。
データベースの更新(リストア)完了後、
UIスレッドでリストアの終了メッセージをトースト表示します。

ブログランキング・にほんブログ村へ

◎これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めてのAndroidのアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、JavaやC#などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。

コメント欄

タイトルとURLをコピーしました