在工作线程中创建Toast

本文详细解析了在工作线程中创建Toast的原理,包括创建Looper对象、消息队列的创建与使用,以及如何通过Looper.loop()实现Toast的显示。并举一反三,提供了一个拖动GridView元素改变位置的示例。

在工作线程中创建Toast,代码如下:

 

private void showToast() {
		new Thread() {
			public void run() {
				
				Looper.prepare();
				Toast.makeText(ActivityA.this, "来自工作线程", 

Toast.LENGTH_SHORT).show();
				Looper.loop();
			}
		}.start();
	}
 

 

 

如果不加Looper.prepare();则会出现以下异常:

java.lang.RuntimeException: Can't create handler inside thread that has not called 

Looper.prepare()
 

 

所以 在工作线程中创建Toast 须创建Looper对象。Looper.prepare();会创建当前线程的Looper对象和对应的MessageQueue(消息队列)

 

 

请参看Toast.java源码。

 Toast.java 包含 这句代码:final Handler mHandler = new Handler();   

mHandler是Toast的一个final类型的成员变量,在Handler的构造方法中有如下代码:

 

if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called 

Looper.prepare()");
        }
 

 

创建了Toast对象后,调用show()方法,把mShow这个Runnable添加到工作线程的消息队列中,

 

消息队列是在创建Looper对象的时候创建好的。

如代码:

 

final Runnable mShow = new Runnable() {
            public void run() {
                handleShow();
            }
        };

public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

/**
     * Causes the Runnable r to be added to the message queue.
     * The runnable will be run on the thread to which this handler is 
     * attached. 
     *  
     */
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
 

此时调用Looper.loop(),从消息队列中取出消息,并执行。

主要源码:

 /**

     *  Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static final void loop() {
        Looper me = myLooper();
        MessageQueue queue = me.mQueue;
        while (true) {
            Message msg = queue.next(); // might block
            ......
                msg.target.dispatchMessage(msg);
               ......
                msg.recycle();
            }
        }
    }
 

此时会调用:mShow的handleShow();

源码:

 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;
                mWM = WindowManagerImpl.getDefault();
                final int gravity = mGravity;
                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;
                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);
            }
        }
 

mWM.addView(mView, mParams);把Toast对应的view添加到当前Window中。

举一反三:

基于以上原理,可以实现这样的效果,拖动GridView里的ImageView元素,改变元素在GridView的位置。

<think>好的,我现在需要解决在安卓开发中在线程内显示Toast失败的问题。根据用户提供的引用资料,我得先理解问题所在。用户提到Toast需要在主线程显示,如果在后台线程直接调用会崩溃或者失败。引用[1]指出Toast必须在主线程执行,否则可能阻塞UI线程。引用[2]提到了Android7.1.1的Toast崩溃问题,可能和线程有关。引用[3]和[4]则给出了通过Handler或HandlerUtil来在子线程中显示Toast的方法。 首先,我需要明确问题原因:在非主线程中直接调用Toast.show()会导致异常,因为Android的UI操作必须在主线程进行。解决方案的关键是将Toast的显示逻辑切换到主线程。 接下来,根据引用资料中的解决方案,可能的解决方法包括使用Handler、Activity.runOnUiThread()或者View.post()。用户提供的HandlerUtil类(引用[4])封装了一个Handler,通过post方法将任务传递到主线程执行。这应该是一个有效的方案。 另外,用户提到的异步消息处理机制(引用[3])可能涉及Looper和Handler机制,需要确保在子线程中正确初始化Handler,或者使用主线程的Handler来发送消息。 可能需要验证这些方法的正确性,并给出具体代码示例。例如,使用Activity.runOnUiThread()或者View.post(),或者在子线程创建Handler时检查Looper是否存在,否则需要准备Looper。 还需要注意,在Android 7.1及更高版本可能存在特定的崩溃问题,需要确保在主线程更新UI,避免直接在其他线程操作。 总结解决方案步骤:1. 识别当前是否在主线程;2. 如果不在,使用Handler、runOnUiThread等方法切换到主线程显示Toast。 最后,生成相关问题,如如何判断当前线程、其他主线程通信方式等,帮助用户深入理解。</think>### 安卓子线程Toast显示失败的解决方案 在Android开发中,若直接在子线程中调用`Toast.show()`会导致应用崩溃或显示失败,因为**Toast的UI操作必须在主线程(UI线程)执行**。以下是具体原因与解决方案: --- #### **原因分析** 1. **线程限制** Toast的显示依赖Android的UI系统,所有UI操作(包括Toast)必须通过主线程更新[^1]。若在子线程中直接调用`Toast.makeText().show()`,会抛出`CalledFromWrongThreadException`异常[^2]。 2. **Android版本兼容性** 在Android 7.1及以上版本中,Toast的底层实现变更可能导致更频繁的崩溃,尤其是线程切换未正确处理时。 --- #### **解决方案** 以下方法均通过**将Toast显示逻辑切换到主线程**解决问题: ##### 1. **使用`Activity.runOnUiThread()`** 在Activity或Fragment中,直接调用`runOnUiThread()`切换线程: ```java new Thread(() -> { // 子线程代码 runOnUiThread(() -> { Toast.makeText(MainActivity.this, "提示信息", Toast.LENGTH_SHORT).show(); }); }).start(); ``` ##### 2. **通过`Handler`传递消息** 在主线程初始化`Handler`,并在子线程中发送消息: ```java // 主线程中初始化 private Handler mHandler = new Handler(Looper.getMainLooper()); // 子线程中调用 new Thread(() -> { mHandler.post(() -> { Toast.makeText(context, "提示信息", Toast.LENGTH_SHORT).show(); }); }).start(); ``` ##### 3. **使用`View.post()`** 通过任意视图控件的`post()`方法切换到主线程: ```java new Thread(() -> { someView.post(() -> { Toast.makeText(context, "提示信息", Toast.LENGTH_SHORT).show(); }); }).start(); ``` ##### 4. **封装工具类(推荐)** 引用[^4]提供的`HandlerUtil`工具类,简化线程切换: ```java // 初始化(在Application或主Activity中) HandlerUtil.init(); // 子线程中调用 HandlerUtil.post(() -> { Toast.makeText(context, "提示信息", Toast.LENGTH_SHORT).show(); }); ``` --- #### **注意事项** - **Looper准备**:若子线程需独立使用`Handler`,需先调用`Looper.prepare()`和`Looper.loop()`,但此方法复杂度较高,推荐直接依赖主线程的`Handler`[^3]。 - **Toast频率控制**:避免高频触发Toast,否则可能影响用户体验。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值