Android中AlertDialog调用show()后未立即显示的问题排查
这个问题其实是Android主线程的消息队列机制在搞鬼,我来给你捋清楚:
问题根源
Android的所有UI操作(比如显示AlertDialog)都依赖主线程(UI线程)处理,而主线程是基于消息队列工作的——所有UI相关的请求都会被打包成消息,放到队列里排队等待执行。
当你在onClickChooseFolder方法里调用alert.show()时,只是把「显示弹窗」这个消息发送到了主线程的消息队列中,但此时主线程正忙着执行方法里的后续代码:比如权限检查、创建DirectoryChooserDialog、遍历文件夹统计文件数量这些逻辑。只有当整个onClickChooseFolder方法执行完毕,主线程才会空闲下来,去处理消息队列里的「显示弹窗」请求,这就是为什么弹窗会延迟出现。
解决方案
核心思路是让「显示弹窗」的消息优先被处理,把后续的非紧急/耗时逻辑放到消息队列的后面,或者放到子线程中执行(注意子线程不能直接做UI操作)。这里给你两种可行的方案:
方案1:用Handler.post()延迟执行后续代码
把alert.show()之后的所有代码包裹到Handler.post()中,这样这些代码会被放到消息队列的末尾,让主线程先处理「显示弹窗」的请求:
public void onClickChooseFolder(final View v) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Confirm"); builder.setMessage("Is your folder on an external SD card?"); builder.setPositiveButton("YES", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Do something here } }); builder.setNegativeButton("NO", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Do something here } }); AlertDialog alert = builder.create(); alert.show(); // 把后续代码放到Handler.post()里,让弹窗先显示 new Handler().post(new Runnable() { @Override public void run() { int permission = ContextCompat.checkSelfPermission(SourceFolders.this, Manifest.permission.READ_EXTERNAL_STORAGE); if (permission != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "Permission to read external storage denied"); } permission = ContextCompat.checkSelfPermission(SourceFolders.this, Manifest.permission.WRITE_EXTERNAL_STORAGE); if (permission != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "Permission to write external storage denied"); } String m_chosenDir = ""; Boolean m_newFolderEnabled = false; // Make the folder list ListView visible DirectoryChooserDialog directoryChooserDialog = new DirectoryChooserDialog(SourceFolders.this, new DirectoryChooserDialog.ChosenDirectoryListener() { @Override public void onChosenDir(String chosenDir) { String sourceFolder = chosenDir; //Check whether the chosen folder is empty or invalid File f = new File(sourceFolder); File file[] = f.listFiles(); //If file is null then the source folder isn't valid if (file == null) { Toast.makeText(SourceFolders.this, "Selected folder is not valid", Toast.LENGTH_LONG).show(); return; } int fileCount = file.length; if (fileCount < 1) { Toast.makeText(SourceFolders.this, "Selected folder is empty", Toast.LENGTH_LONG).show(); } // Add the folder to the folder list // If this is not the paid version of the app then clear the folder list before adding this to it // (Only one folder allowed in free version) if (Utilities.upLoadFiles(SourceFolders.this) < 10) { folderlist.clear(); } //save the folder list to source_folder preference folderlist.add(sourceFolder); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SourceFolders.this); SharedPreferences.Editor edit = prefs.edit(); Set<String> set = new HashSet<String>(); set.addAll(folderlist); edit.putStringSet("source_folders", set); edit.commit(); refreshListViewFromPrefs(); setListViewOnClickListener(); String fileName; int imageCount = 0; int videoCount = 0; for (int i = 0; i < file.length; i++) { fileName = file[i].getName(); if (fileName.endsWith("jpg") || fileName.endsWith("JPG")|| fileName.endsWith("jpeg")|| fileName.endsWith("JPEG")) { imageCount++; } if (fileName.endsWith("mp4") || fileName.endsWith("MP4")|| fileName.endsWith("avi")|| fileName.endsWith("AVI")) { videoCount++; } } if (imageCount>0 || videoCount>0) { Toast.makeText(SourceFolders.this, "Selected folder contains " + imageCount + " photo(s) and " + videoCount + " video(s)", Toast.LENGTH_LONG).show(); return; } else { Toast.makeText(SourceFolders.this, "Selected folder contains no photos or videos", Toast.LENGTH_LONG).show(); return; } } }); directoryChooserDialog.setNewFolderEnabled(m_newFolderEnabled); directoryChooserDialog.chooseDirectory(m_chosenDir); m_newFolderEnabled = ! m_newFolderEnabled; } }); }
方案2:把耗时操作放到子线程
如果后续代码里有明显的耗时逻辑(比如遍历大量文件统计数量),更推荐把这些操作放到子线程中执行,避免阻塞主线程引发ANR(应用无响应)。注意子线程中如果需要更新UI(比如显示Toast、更新ListView),要切换回主线程:
比如把onChosenDir里的文件统计逻辑放到子线程:
@Override public void onChosenDir(String chosenDir) { new Thread(new Runnable() { @Override public void run() { String sourceFolder = chosenDir; File f = new File(sourceFolder); File file[] = f.listFiles(); if (file == null) { // 切换回主线程显示Toast runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(SourceFolders.this, "Selected folder is not valid", Toast.LENGTH_LONG).show(); } }); return; } int fileCount = file.length; if (fileCount < 1) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(SourceFolders.this, "Selected folder is empty", Toast.LENGTH_LONG).show(); } }); } // 统计文件数量的耗时操作 int imageCount = 0; int videoCount = 0; for (int i = 0; i < file.length; i++) { String fileName = file[i].getName(); if (fileName.endsWith("jpg") || fileName.endsWith("JPG")|| fileName.endsWith("jpeg")|| fileName.endsWith("JPEG")) { imageCount++; } if (fileName.endsWith("mp4") || fileName.endsWith("MP4")|| fileName.endsWith("avi")|| fileName.endsWith("AVI")) { videoCount++; } } // 切换回主线程更新UI和显示Toast runOnUiThread(new Runnable() { @Override public void run() { // 处理文件夹列表更新逻辑 if (Utilities.upLoadFiles(SourceFolders.this) < 10) { folderlist.clear(); } folderlist.add(sourceFolder); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SourceFolders.this); SharedPreferences.Editor edit = prefs.edit(); Set<String> set = new HashSet<String>(); set.addAll(folderlist); edit.putStringSet("source_folders", set); edit.commit(); refreshListViewFromPrefs(); setListViewOnClickListener(); // 显示统计结果Toast if (imageCount>0 || videoCount>0) { Toast.makeText(SourceFolders.this, "Selected folder contains " + imageCount + " photo(s) and " + videoCount + " video(s)", Toast.LENGTH_LONG).show(); } else { Toast.makeText(SourceFolders.this, "Selected folder contains no photos or videos", Toast.LENGTH_LONG).show(); } } }); } }).start(); }
补充说明
- 不管用哪种方案,核心都是不要让主线程长时间执行同步代码,否则不仅会导致弹窗延迟,还可能触发ANR错误。
AlertDialog.show()本身是异步请求显示,不是立即执行绘制,必须等主线程空闲才能处理。
内容的提问来源于stack exchange,提问作者Ian M
相关产品推荐
相关产品推荐

