こんにちは、まっさん(@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での開発を始めたい方へ
アプリケーション開発経験がない方や、アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、Android のアプリ開発ができるようになるには、かなりの時間がかかります。オンラインスクールでの習得を、強くおススメします。
未経験者からシステムエンジニアを目指すのに最適かと、
まずは無料相談から♪
未経験者からプログラマーを目指すのに最適かと、
まずは無料カウンセリングから♪
カリキュラムとサポートがしっかりしています、
お得なキャンペーンとかいろいろやっています♪
ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、
まずは無料オンライン相談から♪
参考になったら、💛をポッチとしてね♪