AsyncTask执行loadVideos报initLoader需在主线程调用错误如何解决
问题根因
你遇到的报错核心原因是:调用的FileFilter.getFiles()方法内部依赖了LoaderManager.initLoader(),Android系统要求LoaderManager的初始化和相关操作必须在主线程执行,而你现在把loadVideos()放在AsyncTask的doInBackground()回调中执行,这个回调运行在后台工作线程,不符合API要求所以抛出异常。
解决方案
推荐采用逻辑拆分的方案,将Loader相关的调用移到主线程,耗时的文件遍历、数据组装、数据库操作保留在后台执行,既满足API调用要求,也不会阻塞主线程:
// 先在主线程调用FileFilter.getFiles,拿到文件目录结果 FileFilter.getFiles(this, directories -> { // 拿到结果后再启动AsyncTask执行耗时操作 new LoadVideos(directories).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }, new String[]{".mp4",".ts",".mkv",".mov", ".3gp",".mv2",".m4v",".webm",".mpeg1",".mpeg2",".mts",".ogm", ".bup", ".dv",".flv",".m1v",".m2ts",".mpeg4",".vlc",".3g2", ".avi",".mpeg",".mpg",".wmv",".asf"}); // 改造LoadVideos类,构造函数传入获取到的目录列表 @SuppressLint("StaticFieldLeak") private class LoadVideos extends AsyncTask<Void, Integer, Void> { private List<Directory<NormalFile>> directoryList; public LoadVideos(List<Directory<NormalFile>> directoryList) { this.directoryList = directoryList; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); startActivity(new Intent(SplashScreen.this, MainActivity.class)); } @Override protected Void doInBackground(Void... voids) { // 把原来回调中的耗时逻辑移到后台线程执行 for (Directory<NormalFile> directory : directoryList) { for (int i = 0; i < directory.getFiles().size(); i++) { File file = new File(directory.getFiles().get(i).getPath()); Log.d(TAG, "onResult: " + file.getPath()); FileStore fs = db.getFileByCustomPath(file.getAbsolutePath()); AllVideoModel avm = new AllVideoModel(); avm.setFileName(file.getName()); avm.setVideoPath(file.getAbsolutePath()); avm.setFolderName(Objects.requireNonNull(file.getParentFile()).getName()); avm.setVideoSize(file.length()); avm.setParentPath(file.getParent()); StaticConstant.allMediaListWithModel.add(avm); StaticConstant.FABbeforeSort.add(avm); if (fs == null) { // 数据库操作保留在后台执行 FileStore fsNew = new FileStore(); fsNew.setName(file.getName()); fsNew.setFilePath(file.getAbsolutePath()); fsNew.setParent(Objects.requireNonNull(file.getParentFile()).getName()); fsNew.setUpdatedAt(new Date()); fsNew.setParentPath(file.getParent()); db.insertOrUpdateFile(fsNew); } } } return null; } }
如果你不想调整原有逻辑结构,也可以临时把FileFilter.getFiles()的调用切换到主线程执行,不过不推荐该方案,因为回调中的耗时操作如果在主线程执行会导致界面卡顿:
@Override protected Void doInBackground(Void... voids) { // 切换到主线程执行依赖Loader的代码 new Handler(Looper.getMainLooper()).post(() -> { loadVideos(); }); return null; }
额外说明
AsyncTask已经在Android API 30中被正式废弃,后续维护时可以考虑使用ThreadPoolExecutor搭配Handler、WorkManager或者Kotlin协程实现异步任务,兼容性和可维护性更好。
内容的提问来源于stack exchange,提问作者sanoj lawrence
相关产品推荐
相关产品推荐

