我写了一个子线程,在里面做了一些图片的操作,操作结束的时候需要弹出toast来提示一下状态,代码如下:
new Thread(new Runnable() {
@Override
public void run() {
try {
InputStream is = getAssets().open("picture/"+TEST_FILE_NAME);
FileOutputStream fos = new FileOutputStream(testImageOnSdCard);
byte[] buffer = new byte[8192];
int read;
try {
while ((read = is.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
Log.e("test","成功将图片从Asset目录下写入到SD卡目录下");
Toast.makeText(AUILActivity.this,"成功将图片从Asset目录下写入到SD卡目录下",Toast.LENGTH_SHORT).show();
} finally {
fos.flush();
fos.close();
is.close();
}
} catch (IOException e) {
Log.e("test","失败将图片从Asset目录下写入到SD卡目录下");
Toast.makeText(AUILActivity.this,"将图片从Asset目录下写入到SD卡目录下失败",Toast.LENGTH_SHORT).show();
}
}
}).start();
然后报了下面这个错误:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.widget.Toast$TN.<init>(Toast.java:354)
at android.widget.Toast.<init>(Toast.java:101)
at android.widget.Toast.makeText(Toast.java:266)
显而易见,如果没有调用Looper.prepare()这个方法,这无法在线程中创建handler。在Toast.make()中toast的实例化出现了问题
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast result = new Toast(context);//就是他,他出了问题,往下看
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
那就再看看Toast的构造方法:
public Toast(Context context) {
mContext = context;//上下文没什么污点,那问题应该是下面这个兄弟 TN 我差点看成TNT 先看看TN是个什么玩意儿
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
TN源码
private static class TN extends ITransientNotification.Stub {//这里可以看出TN是继承与Binder,用于和系统进程间进行通讯的
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
final Handler mHandler = new Handler();//因为我们报的错误就是没有创建Handler 那么我们再看看handler的创建方法中出了啥子问题
int mGravity;
int mX, mY;
float mHorizontalMargin;
float mVerticalMargin;
View mView;
View mNextView;
int mDuration;
WindowManager mWM;
static final long SHORT_DURATION_TIMEOUT = 5000;
static final long LONG_DURATION_TIMEOUT = 1000;
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
public void handleShow() {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// We can resolve the Gravity here by using the Locale for getting
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.removeTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
private void trySendAccessibilityEvent() {
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(mView.getContext());
if (!accessibilityManager.isEnabled()) {
return;
}
// treat toasts as notifications since they are used to
// announce a transient piece of information to the user
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
event.setClassName(getClass().getName());
event.setPackageName(mView.getContext().getPackageName());
mView.dispatchPopulateAccessibilityEvent(event);
accessibilityManager.sendAccessibilityEvent(event);
}
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
// note: checking parent() just to make sure the view has
// been added... i have seen cases where we get here when
// the view isn't yet added, so let's try not to crash.
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
mView = null;
}
}
}
再贴一下Handler的构造方法
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");//哈哈,这就是我们要找的错误。隐藏的好深。
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看出,在toast的构造方法的时候需要传入当前线程的Looper对象以便于创建handler来进行通讯,如果该对象不存在,那么就会抛出以上异常。
针对这个问题,我在网上查找了一下资料,发现很多人都用了以下方式来解决这个问题:
Looper.prepare();
Toast.makeText(aActivity.this,"test",Toast.LENGTH_SHORT).show();
Looper.loop();
这样的确是可以弹出toast,但是这样也带来的更大的问题。调用Looper.loop();后,
子线程不会终止,会一直运行。如果真需要使用这种方式来弹出toast的话,请在以上的代码后面再加上 Loop.quite()。感谢一口仨馍提出这个问题。
车到山前必有路,病树前头万木春。
总有办法来解决,至少用Handler.postRunable就可以优雅的弹出toast了,或者用handlerMessage也可以。
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(AUILActivity.this,"成功将图片从Asset目录下写入到SD卡目录下",Toast.LENGTH_SHORT).show();
}
});
这下子toast终于弹出来了。
此外,还可以用runOnUiThread();这种方式来弹出toast,亲测可用。
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(AUILActivity.this,"成功将图片从Asset目录下写入到SD卡目录下",Toast.LENGTH_SHORT).show();
}
});
想怎么弹就怎么弹。