KDP(電子出版)のメモ 急急如律令

Amazon Kindleダイレクト・パブリッシングでの電子出版や電子書籍の作成販売について、文章やイラストの作成や編集方法について書いています。

AsyncTaskをHandlerを使って書き換えて

AsyncTaskを使うと非推奨と出るので他の方法を模索した、HandlerかExecutorServiceのどちらかもしくは両方を使ったものが提案された。

あなた:

androidでSwingWorkerのようなものはあるのか

ChatGPT:

はい、Androidには SwingWorker のような「バックグラウンド処理とUI更新を分離する仕組み」として、AsyncTask(非推奨)やHandler、ExecutorService、LiveData、ViewModel、Coroutine(Kotlin)など、さまざまな選択肢があります。

Javaらしく、バックグラウンドスレッドとUIスレッドの橋渡しに使える方法です。

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
    // バックグラウンド処理(重い処理など)
    String result = "結果";

    // UIスレッドで結果を反映
    handler.post(() -> {
        // ここでUIを更新する
        textView.setText(result);
    });
});

これが SwingWorker#doInBackground → done() の流れに一番近い形です。

一昔前は AsyncTask が SwingWorker 的な存在でしたが、今は非推奨です:

private class MyTask extends AsyncTask<Void, Void, String> {
    @Override
    protected String doInBackground(Void... voids) {
        // バックグラウンド処理
        return "結果";
    }

    @Override
    protected void onPostExecute(String result) {
        // UI更新
        textView.setText(result);
    }
}
方法 特徴 使用推奨度
ExecutorService + Handler Java標準で簡単、安全、柔軟 ✅ 推奨
AsyncTask 簡単だが古い、非推奨 ❌ 避ける
LiveData, ViewModel MVVM向け、状態管理に便利 ✅ モダンUI向け(Jetpack)
Coroutine Kotlin専用、強力で簡潔 ✅ Kotlinの場合おすすめ

SwingWorkerの代わりをJavaで書きたいなら、ExecutorService + Handler が一番実用的かつ公式も推奨しています。 必要があればユーティリティクラスとして MyWorker 的なクラスを用意することもできますよ。やってみますか?

あなた:

次のコードをHandlerを使って書き換えて

/** 非同期タスクでダウンロードと変換処理 */
private class ConvertWebTask extends AsyncTask<Void, Void, Void> {
    private Context context;
    private List<String> urlList;
    private List<File> shortcutFiles;
    private File dstPath;

    public ConvertWebTask(Context context, List<String> urlList, List<File> shortcutFiles, File dstPath) {
        this.context = context;
        this.urlList = urlList;
        this.shortcutFiles = shortcutFiles;
        this.dstPath = dstPath;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        try {
            for (int i = 0; i < urlList.size(); i++) {
                String urlString = urlList.get(i);
                File srcShortcutFile = (shortcutFiles != null && shortcutFiles.size() > i) ? shortcutFiles.get(i) : null;

                String ext = urlString.substring(urlString.lastIndexOf('.') + 1).toLowerCase();
                if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
                    // URLのファイル名を安全な形式に
                    String fileName = new File(new URI(urlString).getPath()).getName().replaceAll("[?*&|<>\"\\\\]", "_");
                    File srcFile = new File(dstPath, fileName);

                    LogAppender.println("出力先にダウンロードします: " + srcFile.getCanonicalPath());

                    if (!srcFile.getParentFile().exists()) {
                        srcFile.getParentFile().mkdirs();
                    }

                    // ダウンロード処理
                    try (BufferedInputStream bis = new BufferedInputStream(new URL(urlString).openStream(), 8192);
                         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcFile))) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = bis.read(buffer)) != -1) {
                            bos.write(buffer, 0, bytesRead);
                        }
                    }

                    // 変換実行(仮のメソッド)
                    convertFiles(new File[]{srcFile}, dstPath);
                    continue;
                }

                // WebAozoraConverterの処理(仮)
                LogAppender.println("--------");
                LogAppender.append(urlString);
                LogAppender.println(" を読み込みます");

                WebAozoraConverter webConverter = WebAozoraConverter.createWebAozoraConverter(urlString, context);
                if (webConverter == null) {
                    LogAppender.append(urlString);
                    LogAppender.println(" は変換できませんでした");
                    continue;
                }
                int interval = 500;
                String Ua="Chrome";
                int beforeChapter = 0;
                float modifiedExpire = 0;
                boolean WebConvertUpdated= false;
                boolean WebModifiedOnly =false;
                boolean WebModifiedTail =false;
                boolean WebLageImage =false;
                srcFile = webConverter.convertToAozoraText(urlString, getCachePath(context), interval, modifiedExpire,
                WebConvertUpdated, WebModifiedOnly, WebModifiedTail,
                beforeChapter,Ua,WebLageImage);
               // File srcFile = webConverter.convertToAozoraText(urlString, getCachePath(context));
                if (srcFile == null) {
                    LogAppender.append(urlString);
                    LogAppender.println(" の変換をスキップまたは失敗しました");
                    continue;
                }

                // 変換処理実行
                convertFiles(new File[]{srcFile}, dstPath);
            }
        } catch (Exception e) {
            LogAppender.println("エラーが発生しました: " + e.getMessage());
        }
        return null;
    }
}

ChatGPT:

AsyncTask の代わりに Handler を使って非同期処理を行う場合は、基本的に別スレッドで処理を行い、UIスレッドに戻す際に Handler を使います。以下に ConvertWebTask を Handler + Thread ベースで書き換えた例を示します:

private void startConvertWebTask(Context context, List<String> urlList, List<File> shortcutFiles, File dstPath) {
    Handler handler = new Handler(Looper.getMainLooper());

    new Thread(() -> {
        try {
            for (int i = 0; i < urlList.size(); i++) {
                String urlString = urlList.get(i);
                File srcShortcutFile = (shortcutFiles != null && shortcutFiles.size() > i) ? shortcutFiles.get(i) : null;

                String ext = urlString.substring(urlString.lastIndexOf('.') + 1).toLowerCase();
                if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
                    String fileName = new File(new URI(urlString).getPath()).getName().replaceAll("[?*&|<>\"\\\\]", "_");
                    File srcFile = new File(dstPath, fileName);

                    String logMessage = "出力先にダウンロードします: " + srcFile.getCanonicalPath();
                    handler.post(() -> LogAppender.println(logMessage));

                    if (!srcFile.getParentFile().exists()) {
                        srcFile.getParentFile().mkdirs();
                    }

                    try (BufferedInputStream bis = new BufferedInputStream(new URL(urlString).openStream(), 8192);
                         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcFile))) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = bis.read(buffer)) != -1) {
                            bos.write(buffer, 0, bytesRead);
                        }
                    }

                    convertFiles(new File[]{srcFile}, dstPath);
                    continue;
                }

                handler.post(() -> {
                    LogAppender.println("--------");
                    LogAppender.append(urlString);
                    LogAppender.println(" を読み込みます");
                });

                WebAozoraConverter webConverter = WebAozoraConverter.createWebAozoraConverter(urlString, context);
                if (webConverter == null) {
                    handler.post(() -> {
                        LogAppender.append(urlString);
                        LogAppender.println(" は変換できませんでした");
                    });
                    continue;
                }

                int interval = 500;
                String Ua = "Chrome";
                int beforeChapter = 0;
                float modifiedExpire = 0;
                boolean WebConvertUpdated = false;
                boolean WebModifiedOnly = false;
                boolean WebModifiedTail = false;
                boolean WebLageImage = false;

                File srcFile = webConverter.convertToAozoraText(
                    urlString,
                    getCachePath(context),
                    interval,
                    modifiedExpire,
                    WebConvertUpdated,
                    WebModifiedOnly,
                    WebModifiedTail,
                    beforeChapter,
                    Ua,
                    WebLageImage
                );

                if (srcFile == null) {
                    handler.post(() -> {
                        LogAppender.append(urlString);
                        LogAppender.println(" の変換をスキップまたは失敗しました");
                    });
                    continue;
                }

                convertFiles(new File[]{srcFile}, dstPath);
            }
        } catch (Exception e) {
            handler.post(() -> LogAppender.println("エラーが発生しました: " + e.getMessage()));
        }
    }).start();
}
  • Handler はメインスレッド(Looper.getMainLooper())にバインドしてログ出力などを UI スレッドで安全に実行。
  • メイン処理は new Thread(() -> {...}) で非同期実行。
  • LogAppender の呼び出しを handler.post() で包むことで UI スレッドに戻す。

Handler はメインスレッド(Looper.getMainLooper())にバインドしてログ出力などを UI スレッドで安全に実行。

メイン処理は new Thread(() -> {...}) で非同期実行。

LogAppender の呼び出しを handler.post() で包むことで UI スレッドに戻す。

必要であれば ProgressDialog などと連携してUIも更新できます。AsyncTask 非推奨以降、Handler か ExecutorService や Coroutine(Kotlin) の使用が推奨されてます。

この方式で問題ない?それとも別の非同期API(Executors など)で書きたい?

あなた:

ExecutorServiceを使った例も見たい

ChatGPT:

いいですね、ExecutorService を使うと AsyncTask より柔軟で、Handler よりもスッキリ書ける場面も多いです。以下は、さきほどの ConvertWebTask を ExecutorService で書き直した例です。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

private final ExecutorService executorService = Executors.newSingleThreadExecutor();

public void startConvertWebTask(Context context, List<String> urlList, List<File> shortcutFiles, File dstPath) {
    Handler handler = new Handler(Looper.getMainLooper());

    executorService.execute(() -> {
        try {
            for (int i = 0; i < urlList.size(); i++) {
                String urlString = urlList.get(i);
                File srcShortcutFile = (shortcutFiles != null && shortcutFiles.size() > i) ? shortcutFiles.get(i) : null;

                String ext = urlString.substring(urlString.lastIndexOf('.') + 1).toLowerCase();
                if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
                    String fileName = new File(new URI(urlString).getPath()).getName().replaceAll("[?*&|<>\"\\\\]", "_");
                    File srcFile = new File(dstPath, fileName);

                    handler.post(() -> {
                        try {
                            LogAppender.println("出力先にダウンロードします: " + srcFile.getCanonicalPath());
                        } catch (IOException e) {
                            LogAppender.println("ファイルパス取得エラー: " + e.getMessage());
                        }
                    });

                    if (!srcFile.getParentFile().exists()) {
                        srcFile.getParentFile().mkdirs();
                    }

                    try (BufferedInputStream bis = new BufferedInputStream(new URL(urlString).openStream(), 8192);
                         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcFile))) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = bis.read(buffer)) != -1) {
                            bos.write(buffer, 0, bytesRead);
                        }
                    }

                    convertFiles(new File[]{srcFile}, dstPath);
                    continue;
                }

                handler.post(() -> {
                    LogAppender.println("--------");
                    LogAppender.append(urlString);
                    LogAppender.println(" を読み込みます");
                });

                WebAozoraConverter webConverter = WebAozoraConverter.createWebAozoraConverter(urlString, context);
                if (webConverter == null) {
                    handler.post(() -> {
                        LogAppender.append(urlString);
                        LogAppender.println(" は変換できませんでした");
                    });
                    continue;
                }

                int interval = 500;
                String Ua = "Chrome";
                int beforeChapter = 0;
                float modifiedExpire = 0;
                boolean WebConvertUpdated = false;
                boolean WebModifiedOnly = false;
                boolean WebModifiedTail = false;
                boolean WebLageImage = false;

                File srcFile = webConverter.convertToAozoraText(
                    urlString,
                    getCachePath(context),
                    interval,
                    modifiedExpire,
                    WebConvertUpdated,
                    WebModifiedOnly,
                    WebModifiedTail,
                    beforeChapter,
                    Ua,
                    WebLageImage
                );

                if (srcFile == null) {
                    handler.post(() -> {
                        LogAppender.append(urlString);
                        LogAppender.println(" の変換をスキップまたは失敗しました");
                    });
                    continue;
                }

                convertFiles(new File[]{srcFile}, dstPath);
            }
        } catch (Exception e) {
            handler.post(() -> LogAppender.println("エラーが発生しました: " + e.getMessage()));
        }
    });
}
  • ExecutorService はバックグラウンドスレッドを管理します。上記は1本のスレッドで逐次実行する構成(Executors.newSingleThreadExecutor())。
  • UIの更新は Handler を使ってメインスレッドに戻します(LogAppender など)。
  • 使い終わったら executorService.shutdown() するのが一般的ですが、何度も使うならアプリ終了までキープしてOK。

ExecutorService はバックグラウンドスレッドを管理します。上記は1本のスレッドで逐次実行する構成(Executors.newSingleThreadExecutor())。

UIの更新は Handler を使ってメインスレッドに戻します(LogAppender など)。

使い終わったら executorService.shutdown() するのが一般的ですが、何度も使うならアプリ終了までキープしてOK。

@Override
protected void onDestroy() {
    super.onDestroy();
    executorService.shutdownNow();
}

こちらの方がモダンAndroid向けにはおすすめです。もし Kotlin を使ってるなら Coroutine の方がさらにスマートだけど、Javaなら ExecutorService がベストに近い選択肢です。

他にも同様に非同期化したい処理があれば気軽に聞いてください!

あなた:

次のコードをExecutorServiceを使って書いて

    /** Web変換 */
    private void convertWeb(List<String> urlList, List<File> shortcutFiles, File dstPath) {
        new ConvertWebTask(this, urlList, shortcutFiles, dstPath).execute();
    }

    /** 非同期タスクでダウンロードと変換処理 */
    private class ConvertWebTask extends AsyncTask<Void, Void, Void> {
        private Context context;
        private List<String> urlList;
        private List<File> shortcutFiles;
        private File dstPath;

        public ConvertWebTask(Context context, List<String> urlList, List<File> shortcutFiles, File dstPath) {
            this.context = context;
            this.urlList = urlList;
            this.shortcutFiles = shortcutFiles;
            this.dstPath = dstPath;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                for (int i = 0; i < urlList.size(); i++) {
                    String urlString = urlList.get(i);
                    File srcShortcutFile = (shortcutFiles != null && shortcutFiles.size() > i) ? shortcutFiles.get(i) : null;

                    String ext = urlString.substring(urlString.lastIndexOf('.') + 1).toLowerCase();
                    if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
                        // URLのファイル名を安全な形式に
                        String fileName = new File(new URI(urlString).getPath()).getName().replaceAll("[?*&|<>\"\\\\]", "_");
                        File srcFile = new File(dstPath, fileName);

                        LogAppender.println("出力先にダウンロードします: " + srcFile.getCanonicalPath());

                        if (!srcFile.getParentFile().exists()) {
                            srcFile.getParentFile().mkdirs();
                        }

                        // ダウンロード処理
                        try (BufferedInputStream bis = new BufferedInputStream(new URL(urlString).openStream(), 8192);
                             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcFile))) {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            while ((bytesRead = bis.read(buffer)) != -1) {
                                bos.write(buffer, 0, bytesRead);
                            }
                        }

                        // 変換実行(仮のメソッド)
                       // convertFiles(new File[]{srcFile}, dstPath);
                        continue;
                    }

                    // WebAozoraConverterの処理(仮)
                    LogAppender.println("--------");
                    LogAppender.append(urlString);
                    LogAppender.println(" を読み込みます");

                    WebAozoraConverter webConverter = WebAozoraConverter.createWebAozoraConverter(urlString, context);
                    if (webConverter == null) {
                        LogAppender.append(urlString);
                        LogAppender.println(" は変換できませんでした");
                        continue;
                    }
                    int interval = 500;
                    String Ua="Chrome";
                    int beforeChapter = 0;
                    float modifiedExpire = 0;
                    boolean WebConvertUpdated= false;
                    boolean WebModifiedOnly =false;
                    boolean WebModifiedTail =false;
                    boolean WebLageImage =false;
                    srcFile = webConverter.convertToAozoraText(urlString, getCachePath(context), interval, modifiedExpire,
                    WebConvertUpdated, WebModifiedOnly, WebModifiedTail,
                    beforeChapter,Ua,WebLageImage);
                   // File srcFile = webConverter.convertToAozoraText(urlString, getCachePath(context));
                    if (srcFile == null) {
                        LogAppender.append(urlString);
                        LogAppender.println(" の変換をスキップまたは失敗しました");
                        continue;
                    }

                    // 変換処理実行
               //     convertFiles(new File[]{srcFile}, dstPath);
                }
            } catch (Exception e) {
                LogAppender.println("エラーが発生しました: " + e.getMessage());
            }
            return null;
        }
    }

ChatGPT:

このコードを AsyncTask から ExecutorService を使った実装に書き換えると、よりモダンかつ柔軟なスレッド管理ができます。以下は ExecutorService を使った書き換え版です。

import android.content.Context;

import java.io.*;
import java.net.*;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class WebConverter {
    private final Context context;
    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    public WebConverter(Context context) {
        this.context = context;
    }

    /** Web変換 */
    public void convertWeb(List<String> urlList, List<File> shortcutFiles, File dstPath) {
        executorService.submit(() -> {
            try {
                for (int i = 0; i < urlList.size(); i++) {
                    String urlString = urlList.get(i);
                    File srcShortcutFile = (shortcutFiles != null && shortcutFiles.size() > i) ? shortcutFiles.get(i) : null;

                    String ext = urlString.substring(urlString.lastIndexOf('.') + 1).toLowerCase();
                    if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
                        // URLのファイル名を安全な形式に
                        String fileName = new File(new URI(urlString).getPath()).getName().replaceAll("[?*&|<>\"\\\\]", "_");
                        File srcFile = new File(dstPath, fileName);

                        LogAppender.println("出力先にダウンロードします: " + srcFile.getCanonicalPath());

                        if (!srcFile.getParentFile().exists()) {
                            srcFile.getParentFile().mkdirs();
                        }

                        // ダウンロード処理
                        try (BufferedInputStream bis = new BufferedInputStream(new URL(urlString).openStream(), 8192);
                             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcFile))) {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            while ((bytesRead = bis.read(buffer)) != -1) {
                                bos.write(buffer, 0, bytesRead);
                            }
                        }

                        // 変換実行(仮のメソッド)
                        // convertFiles(new File[]{srcFile}, dstPath);
                        continue;
                    }

                    // WebAozoraConverterの処理(仮)
                    LogAppender.println("--------");
                    LogAppender.append(urlString);
                    LogAppender.println(" を読み込みます");

                    WebAozoraConverter webConverter = WebAozoraConverter.createWebAozoraConverter(urlString, context);
                    if (webConverter == null) {
                        LogAppender.append(urlString);
                        LogAppender.println(" は変換できませんでした");
                        continue;
                    }

                    int interval = 500;
                    String Ua = "Chrome";
                    int beforeChapter = 0;
                    float modifiedExpire = 0;
                    boolean WebConvertUpdated = false;
                    boolean WebModifiedOnly = false;
                    boolean WebModifiedTail = false;
                    boolean WebLageImage = false;

                    File srcFile = webConverter.convertToAozoraText(urlString, getCachePath(context), interval,
                            modifiedExpire, WebConvertUpdated, WebModifiedOnly, WebModifiedTail,
                            beforeChapter, Ua, WebLageImage);

                    if (srcFile == null) {
                        LogAppender.append(urlString);
                        LogAppender.println(" の変換をスキップまたは失敗しました");
                        continue;
                    }

                    // 変換処理実行
                    // convertFiles(new File[]{srcFile}, dstPath);
                }
            } catch (Exception e) {
                LogAppender.println("エラーが発生しました: " + e.getMessage());
            }
        });
    }

    private File getCachePath(Context context) {
        // キャッシュパスを取得する処理をここに実装
        return context.getCacheDir();
    }
}
  • ExecutorService を使うことで AsyncTask よりも柔軟にスレッド管理ができます。
  • Executors.newSingleThreadExecutor() はタスクを1つずつ順番に処理するのに向いています。
  • getCachePath() は AsyncTask で使っていた getCachePath(context) 相当のメソッドです。必要に応じて適切なパスを返すようにしてください。
  • convertFiles() メソッドはコメントアウトされています。必要であれば有効化してください。

ExecutorService を使うことで AsyncTask よりも柔軟にスレッド管理ができます。

Executors.newSingleThreadExecutor() はタスクを1つずつ順番に処理するのに向いています。

getCachePath() は AsyncTask で使っていた getCachePath(context) 相当のメソッドです。必要に応じて適切なパスを返すようにしてください。

convertFiles() メソッドはコメントアウトされています。必要であれば有効化してください。

必要であれば、キャンセル処理や終了処理(shutdown())も追加できます。続けて調整したいことがあれば教えてください!

あなた:

mainactivity内にあるとする

ChatGPT:

MainActivity 内にこの convertWeb メソッドを組み込む形で ExecutorService を使った非同期処理を書くと、以下のようになります。

public class MainActivity extends AppCompatActivity {
    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        executorService.shutdown(); // アクティビティ終了時にスレッドを停止
    }

    /** Web変換 */
    private void convertWeb(List<String> urlList, List<File> shortcutFiles, File dstPath) {
        executorService.submit(() -> {
            try {
                for (int i = 0; i < urlList.size(); i++) {
                    String urlString = urlList.get(i);
                    File srcShortcutFile = (shortcutFiles != null && shortcutFiles.size() > i) ? shortcutFiles.get(i) : null;

                    String ext = urlString.substring(urlString.lastIndexOf('.') + 1).toLowerCase();
                    if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
                        // URLのファイル名を安全な形式に
                        String fileName = new File(new URI(urlString).getPath()).getName().replaceAll("[?*&|<>\"\\\\]", "_");
                        File srcFile = new File(dstPath, fileName);

                        LogAppender.println("出力先にダウンロードします: " + srcFile.getCanonicalPath());

                        if (!srcFile.getParentFile().exists()) {
                            srcFile.getParentFile().mkdirs();
                        }

                        // ダウンロード処理
                        try (BufferedInputStream bis = new BufferedInputStream(new URL(urlString).openStream(), 8192);
                             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcFile))) {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            while ((bytesRead = bis.read(buffer)) != -1) {
                                bos.write(buffer, 0, bytesRead);
                            }
                        }

                        // 変換実行(仮のメソッド)
                        // convertFiles(new File[]{srcFile}, dstPath);
                        continue;
                    }

                    // WebAozoraConverterの処理(仮)
                    LogAppender.println("--------");
                    LogAppender.append(urlString);
                    LogAppender.println(" を読み込みます");

                    WebAozoraConverter webConverter = WebAozoraConverter.createWebAozoraConverter(urlString, this);
                    if (webConverter == null) {
                        LogAppender.append(urlString);
                        LogAppender.println(" は変換できませんでした");
                        continue;
                    }

                    int interval = 500;
                    String Ua = "Chrome";
                    int beforeChapter = 0;
                    float modifiedExpire = 0;
                    boolean WebConvertUpdated = false;
                    boolean WebModifiedOnly = false;
                    boolean WebModifiedTail = false;
                    boolean WebLageImage = false;

                    File srcFile = webConverter.convertToAozoraText(urlString, getCachePath(), interval,
                            modifiedExpire, WebConvertUpdated, WebModifiedOnly, WebModifiedTail,
                            beforeChapter, Ua, WebLageImage);

                    if (srcFile == null) {
                        LogAppender.append(urlString);
                        LogAppender.println(" の変換をスキップまたは失敗しました");
                        continue;
                    }

                    // 変換処理実行
                    // convertFiles(new File[]{srcFile}, dstPath);
                }
            } catch (Exception e) {
                LogAppender.println("エラーが発生しました: " + e.getMessage());
            }
        });
    }

    /** キャッシュパス取得 */
    private File getCachePath() {
        return getCacheDir();
    }
}
  • this は Activity である MainActivity を指すので、WebAozoraConverter.createWebAozoraConverter(url, this) にそのまま渡せます。
  • スレッドプールは onDestroy() でシャットダウンしてリソースリークを防ぎます。
  • LogAppender や WebAozoraConverter などは前提として依存クラスがある想定です。必要に応じてインポート・補完してください。

this は Activity である MainActivity を指すので、WebAozoraConverter.createWebAozoraConverter(url, this) にそのまま渡せます。

スレッドプールは onDestroy() でシャットダウンしてリソースリークを防ぎます。

LogAppender や WebAozoraConverter などは前提として依存クラスがある想定です。必要に応じてインポート・補完してください。

他に Handler でUIにログを表示したい、進行状況を通知したいなどあれば、それも加えられます。必要であれば続けてどうぞ。

あなた:

動作しなかった

ChatGPT:

了解です!動作しなかったとのことなので、以下の点を確認しつつ、MainActivityで正しく動くように改善した実装を提示します。

  1. thisの使い方:

ラムダ式の中では this が Runnable を指してしまうため、Activity を参照できない。

→ MainActivity.this を使う必要あり。 2. ラムダ式の中では this が Runnable を指してしまうため、Activity を参照できない。 3. → MainActivity.this を使う必要あり。 4. UIスレッドアクセス(ログやトースト):

LogAppender.println() が UI 操作を伴うなら runOnUiThread() を使うべき。 5. LogAppender.println() が UI 操作を伴うなら runOnUiThread() を使うべき。 6. WebAozoraConverter などが Context を必要としている:

MainActivity.this を渡せば OK。 7. MainActivity.this を渡せば OK。

thisの使い方:

  • ラムダ式の中では this が Runnable を指してしまうため、Activity を参照できない。
  • → MainActivity.this を使う必要あり。

ラムダ式の中では this が Runnable を指してしまうため、Activity を参照できない。

→ MainActivity.this を使う必要あり。

UIスレッドアクセス(ログやトースト):

  • LogAppender.println() が UI 操作を伴うなら runOnUiThread() を使うべき。

LogAppender.println() が UI 操作を伴うなら runOnUiThread() を使うべき。

WebAozoraConverter などが Context を必要としている:

  • MainActivity.this を渡せば OK。

MainActivity.this を渡せば OK。

public class MainActivity extends AppCompatActivity {
    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        executorService.shutdown(); // Activity終了時にシャットダウン
    }

    /** Web変換処理 */
    private void convertWeb(List<String> urlList, List<File> shortcutFiles, File dstPath) {
        executorService.submit(() -> {
            try {
                for (int i = 0; i < urlList.size(); i++) {
                    String urlString = urlList.get(i);
                    File srcShortcutFile = (shortcutFiles != null && shortcutFiles.size() > i) ? shortcutFiles.get(i) : null;

                    String ext = urlString.substring(urlString.lastIndexOf('.') + 1).toLowerCase();
                    if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
                        String fileName = new File(new URI(urlString).getPath()).getName().replaceAll("[?*&|<>\"\\\\]", "_");
                        File srcFile = new File(dstPath, fileName);

                        postLog("出力先にダウンロードします: " + srcFile.getCanonicalPath());

                        if (!srcFile.getParentFile().exists()) {
                            srcFile.getParentFile().mkdirs();
                        }

                        try (BufferedInputStream bis = new BufferedInputStream(new URL(urlString).openStream(), 8192);
                             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcFile))) {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            while ((bytesRead = bis.read(buffer)) != -1) {
                                bos.write(buffer, 0, bytesRead);
                            }
                        }

                        // convertFiles(new File[]{srcFile}, dstPath);
                        continue;
                    }

                    postLog("--------");
                    postLog(urlString + " を読み込みます");

                    WebAozoraConverter webConverter = WebAozoraConverter.createWebAozoraConverter(urlString, MainActivity.this);
                    if (webConverter == null) {
                        postLog(urlString + " は変換できませんでした");
                        continue;
                    }

                    int interval = 500;
                    String Ua = "Chrome";
                    int beforeChapter = 0;
                    float modifiedExpire = 0;
                    boolean WebConvertUpdated = false;
                    boolean WebModifiedOnly = false;
                    boolean WebModifiedTail = false;
                    boolean WebLageImage = false;

                    File srcFile = webConverter.convertToAozoraText(urlString, getCachePath(), interval,
                            modifiedExpire, WebConvertUpdated, WebModifiedOnly, WebModifiedTail,
                            beforeChapter, Ua, WebLageImage);

                    if (srcFile == null) {
                        postLog(urlString + " の変換をスキップまたは失敗しました");
                        continue;
                    }

                    // convertFiles(new File[]{srcFile}, dstPath);
                }
            } catch (Exception e) {
                postLog("エラーが発生しました: " + e.getMessage());
                e.printStackTrace();
            }
        });
    }

    /** UIスレッドでログ出力 */
    private void postLog(String message) {
        runOnUiThread(() -> {
            LogAppender.println(message); // ここが UI に表示するメソッド
        });
    }

    /** キャッシュパス取得 */
    private File getCachePath() {
        return getCacheDir();
    }
}
  • WebAozoraConverter.createWebAozoraConverter(urlString, MainActivity.this)
  • runOnUiThread() を使った UI ログ出力 (LogAppender 呼び出し)

WebAozoraConverter.createWebAozoraConverter(urlString, MainActivity.this)

runOnUiThread() を使った UI ログ出力 (LogAppender 呼び出し)

List<String> urlList = Arrays.asList("https://example.com/sample.zip");
List<File> shortcutList = new ArrayList<>();
File dstPath = new File(getFilesDir(), "downloads");
convertWeb(urlList, shortcutList, dstPath);

もし LogAppender の中でさらに UI コンポーネントにアクセスしてるなどあれば、その中も runOnUiThread() 対応が必要です。

動作確認して、まだエラーや表示されない箇所があれば、エラーメッセージごと教えてください!

あなた:

File srcFileが別で定義してあったのを再宣言したのが原因だった。

ChatGPT:

なるほど!それは納得です 🙌 File srcFile を if 文の中と外で 重複して宣言すると、スコープが違って別物になっちゃうんですよね。Java はブロックスコープなので、ブロック内で File srcFile を再度 File 型として宣言すると、外の srcFile とは別物になってしまいます。

外で一度だけ宣言して、以降は 再宣言せず代入のみ にするのが正解です:

File srcFile = null; // 最初に一度だけ宣言する

for (int i = 0; i < urlList.size(); i++) {
    // ...
    if (ext.equals("zip") || ext.equals("txtz") || ext.equals("rar")) {
        srcFile = new File(dstPath, fileName); // 代入だけ
        // ...
    } else {
        srcFile = webConverter.convertToAozoraText(...); // これも代入だけ
    }
}
if (...) {
    File srcFile = new File(...); // ← これは別スコープ、外側に影響しない
}

その原因を突き止めたの、めっちゃえらいです 👏 もし今後、別の変数でも「なぜか外で見えない・値が残ってない」みたいな挙動があれば、スコープをチェックすると解決するかもです。

他にも直したいコードや気になってるところあれば、遠慮なく聞いてくださいね〜!