Can't create handler inside thread that has not called Looper.prepare()类型的错误及修改方法

时间:2022-08-14 20:45:01

 

首先将错误帖出来,(大概很多朋友都碰到过吧):

E/AndroidRuntime( 9874): FATAL EXCEPTION: Thread-1034

E/AndroidRuntime( 9874): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
E/AndroidRuntime( 9874):        at android.os.Handler.<init>(Handler.java:205)
E/AndroidRuntime( 9874):        at android.os.Handler.<init>(Handler.java:119)
E/AndroidRuntime( 9874):        at android.app.Dialog.<init>(Dialog.java:107)
E/AndroidRuntime( 9874):        at android.app.AlertDialog.<init>(AlertDialog.java:119)
E/AndroidRuntime( 9874):        at android.app.AlertDialog$Builder.create(AlertDialog.java:994)
E/AndroidRuntime( 9874):        at com.android.camera.ActivityBase.updateStorageHint(ActivityBase.java:871)
E/AndroidRuntime( 9874):        at com.android.camera.PhotoModule.updateCameraParametersPreference(PhotoModule.java:6274)
E/AndroidRuntime( 9874):        at com.android.camera.PhotoModule.setCameraParameters(PhotoModule.java:7059)
E/AndroidRuntime( 9874):        at com.android.camera.PhotoModule.startPreview(PhotoModule.java:5605)
E/AndroidRuntime( 9874):        at com.android.camera.PhotoModule.access$1400(PhotoModule.java:190)

E/AndroidRuntime( 9874):        at com.android.camera.PhotoModule$CameraStartUpThread.run(PhotoModule.java:627)

从上述调用栈上来看,就知道是一个 CameraStartUpThread 线程中跑了关于UI显示方面的东东导致的。Android app层规定的比较死板,

一般在非 UI 的线程中不允许调弹出框,对话框之类的东东。而在上述错误中正是由于 ActivityBase.java 类的 updateStorageHint() 函数中创建了一个对话框导致的,而这个函数又被非 UI 线程 CameraStartUpThread 间接调用 。

要解决此问题也非常简单:

方法一,将对话框创建的地方放在 UiThread 中跑。

			AlertDialog.Builder builder = new AlertDialog.Builder(this);
			builder.setCancelable(false)
			        .setTitle(getString(R.string.not_enough_dialog_title))
			        .setMessage(message)
			        .setNeutralButton(R.string.not_enough_dialog_clear,
			                new DialogInterface.OnClickListener() {
				                @Override
				                public void onClick(DialogInterface dialog, int which) {
					                startActivity(clearIntent);
				                }
			                })
			        .setNegativeButton(android.R.string.cancel,
			                new DialogInterface.OnClickListener() {
				                @Override
				                public void onClick(DialogInterface dialog, int which) {
					                finish();
				                }
			                });

源代码:

if (mNeedClearRotateDialog == null || !mNeedClearRotateDialog.isShowing()) {
				mNeedClearRotateDialog = builder.create();
			}
改为:

if (mNeedClearRotateDialog == null || !mNeedClearRotateDialog.isShowing()) {
				mActivity.runOnUiThread(new Runnable() {
					public void run() {
						mNeedClearRotateDialog = builder.create();
					}
				});
			}

注:如果当前类不是 Activity 类的话,还需要在 runOnUiThread 前加上 mActivity 类对象来调用,如上所示。另外该 Runnable() 是一个匿名线程,该线程中变量需要是 final 类型的。这里还需要将 builder 变量改为 final 类型的。

方法二:在 CameraStartupThread() 一开始调用的地方就加上runOnUiThread

源码:

        checkStoragePathPreference(mPreferences);
        if (mActivity != null) {
        	Long leftSpace = Storage.getAvailableSpace();
			mActivity.updateStorageHint(leftSpace);
        }

改为:

        checkStoragePathPreference(mPreferences);
        if (mActivity != null) {
        	final Long leftSpace = Storage.getAvailableSpace();
        	mActivity.runOnUiThread(new Runnable() {
				public void run() {        	
					mActivity.updateStorageHint(leftSpace);
				}
        	});
        }
再次重申匿名线程中的变量需要改为 final 类型的,不然会报错。

相对而言,推荐方法一,这样影响的地方比较小,如果在 mAcitivity.updateStorageHint(leftSpace) 函数调用中没有什么复杂的逻辑的话方法二也是可行的。

UiThread 一般都是用于界面显示的一些东西,不要把一些复杂的计算或者耗电的逻辑放到其中。如果有比较耗时的操作的话就建议新开线程来在后台做了。

很多 ANR 都是来源于主线程中跑了一些比较耗时耗资源的操作,导致按键响应不及时,或者等待过久 发生的。