林和环保网

Android中,在子线程使用Toast会报错?

林和环保网 0

一、Android中,在子线程使用Toast会报错?

看了下上面的回答,竟然都认为这个异常是子线程不能执行UI操作导致的,实际上这样的的说法没有错,但并不是这里抛出异常的原因,因为根本还没有执行到更新UI那一步。

其实问题没那么复杂,直接从代码分析原因即可。

先看Toast.makeText(Context,CharSequence,int)的源码:

 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;
    }

这里就是初始化View并给Toast赋值,但是这里并没有涉及Handler,为什么会出现“Can't create handler inside thread that has not called Looper.prepare()”这样的错误呢?

其实是在Toast的构造方法中:

 public Toast(Context context) {
        mContext = context;
        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这个类(这个类名也是没sei了,叫ToastNative也更好呀)的部分代码如下:

    private static class TN extends ITransientNotification.Stub {
        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();    

        int mGravity;
        int mX, mY;
        float mHorizontalMargin;
        float mVerticalMargin;


        View mView;
        View mNextView;

        WindowManager mWM;

        TN() {
          ...
        }

        /**
         * 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);
        }

        ...


    }

注意其中的mHandler这个成员,final Handler mHandler=new Handler(); 会在TN的构造方法之前执行,从而导致在Toast()中抛出异常。

所以上面那些“子线程更新 UI ,需要利用 Handler 切换回到主线程进行操作”的说法没有错,但并不是这里抛出异常的原因,因为根本还没有执行到更新UI那一步。实际上这部分代码也是可以在子线程中执行的,后面会给出我的示例。

为了找出解决方法,就看一下Android中的Toast显示的完整过程吧。Toast#show()的代码如下:

 public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

而getService()的代码如下:

  static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }

非常典型的Binder通信,不啰嗦了,对应的NotificationManagerService代码如下:

public void enqueueToast(String pkg, ITransientNotification callback, int duration)
    {
        if (pkg == null || callback == null) {
            return ;
        }

        synchronized (mToastQueue) {
            int callingPid = Binder.getCallingPid();
            long callingId = Binder.clearCallingIdentity();
            try {
                ToastRecord record;
                int index = indexOfToastLocked(pkg, callback);
                // If it's already in the queue, we update it in place, we don't
                // move it to the end of the queue.
                if (index >= 0) {
                    record = mToastQueue.get(index);
                    record.update(duration);
                } else {
                    record = new ToastRecord(callingPid, pkg, callback, duration);
                    mToastQueue.add(record);
                    index = mToastQueue.size() - 1;
                    keepProcessAliveLocked(callingPid);
                }
                // If it's at index 0, it's the current toast.  It doesn't matter if it's
                // new or just been updated.  Call back and tell it to show itself.
                // If the callback fails, this will remove it from the list, so don't
                // assume that it's valid after this.
                if (index == 0) {
                    showNextToastLocked();
                }
            } finally {
                Binder.restoreCallingIdentity(callingId);
            }
        }
    }

显然,就是让Toast请求进入队列统一管理,而显示下一条Toast的代码如下:

 private void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            try {
                record.callback.show();
                scheduleTimeoutLocked(record, false);
                return;
            } catch (RemoteException e) {
                // remove it from the list and let the process die
                int index = mToastQueue.indexOf(record);
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    }

注意其中的record.callback.show()其实对应的就是TN中的show(),其代码如下:

public void show() {
            mHandler.post(mShow);
        }

显然会调用handleShow()方法:

   public void handleShow() {
            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;
                if (mView.getParent() != null) {
                    mWM.removeView(mView);
                }
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }

显然,这里才是真正显示Toast的地方,这里才真正涉及到了更新UI的操作。

那如果我们要在子线程中进行显示Toast的操作要怎么办,很简单的方案就是利用主线程的handler.post(...)来执行Toast.makeText(...).show()的操作。那有没有其他的方法呢?

其实从刚刚的分析中,我们发现只要在创建Toast()时不让它抛出异常,并且保证TN中的mHandler是基于主线程消息队列的Handler对象即可。

由于ITransientNotification和INotificationManager对应用开发者不可见,故没有办法构造一个可以完成TN功能的类,那就只能从反射入手了。

如下是我的一种解决方案:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                showToast();
            }
        }).start();
    }
    private void showToast(){
        Looper.prepare();
        Toast toast = Toast.makeText(MainActivity.this, "Enjoy your national day", Toast.LENGTH_LONG);
        try {
            Field field = toast.getClass().getDeclaredField("mTN");
            field.setAccessible(true);
            Object obj = field.get(toast);
            setNextView(toast,obj);
            changeHandlerValue(obj);
            enqueueToast(toast,obj);

        } catch (Exception e) {

        }
        Looper.loop();
    }

    private void setNextView(Toast toast,Object tn){
        try{
            Field toastNextView=toast.getClass().getDeclaredField("mNextView");
            toastNextView.setAccessible(true);

            Field nextViewField=tn.getClass().getDeclaredField("mNextView");
            nextViewField.setAccessible(true);
            nextViewField.set(tn,toastNextView.get(toast));
        }catch (Exception ex){
            ex.printStackTrace();
        }

    }

    private void changeHandlerValue(Object tn){
        try{
            Field mHandlerField=tn.getClass().getDeclaredField("mHandler");
            mHandlerField.setAccessible(true);
            mHandlerField.set(tn,new Handler(Looper.getMainLooper()));
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }


    private void enqueueToast(Toast toast,Object tn){
        try{
            Method getServiceMethod=toast.getClass().getDeclaredMethod("getService",null);
            getServiceMethod.setAccessible(true);
            Object obj=getServiceMethod.invoke(null);
            Method[]methods=obj.getClass().getDeclaredMethods();
            Method enqueueMethod=null;
            for(Method method:methods){
                if("enqueueToast".equals(method.getName())){
                    enqueueMethod=method;
                    break;
                }
            }
            if(enqueueMethod==null){
                return;
            }
            enqueueMethod.setAccessible(true);
            enqueueMethod.invoke(obj,"wang.imallen.toastsample",tn,Toast.LENGTH_LONG);
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
    
}

就三个要点:

1) 为了防止在创建TN时抛出异常,需要在子线程中使用Looper.prepare();和Looper.loop();

2)为了使TN中最终调用的Handler对象是基于主线程的,需要使用反射将其替换掉,changeHandlerValue()的作用就是这个;

3)由于ITransientNotification不可见,所以不能通过obj.getClass().getDeclaredMethod("enqueueToast",...)的方法来直接获取到这个Method;

不过,虽然这种方法用另一种思路实现了在子线程中显示Toast的操作,但是非常不推荐这样做,因为对于非public成员的反射是有风险的,万一在某个版本中这个成员的名称换了,这种方法就会出错。

二、Java多线程编程:如何实现主线程等待子线程结束

引言

在Java多线程编程中,有时候我们需要主线程等待所有子线程都执行完毕后再继续执行。本文将介绍几种实现主线程等待子线程结束的方法,帮助您更好地掌握Java多线程编程知识。

方法一:使用Thread的join方法

Thread类中的join方法可以让一个线程等待另一个线程执行完成。主线程可以调用子线程的join方法来等待子线程执行完毕。

示例代码:


Thread thread = new Thread(() -> {
    // 子线程执行的代码
});
thread.start();
try {
    thread.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}

方法二:使用CountDownLatch类

CountDownLatch是Java并发包中的一个工具类,可以用来控制线程的执行顺序。通过将CountDownLatch的计数器初始化为子线程的数量,主线程可以调用await方法等待子线程执行完毕。

示例代码:


CountDownLatch latch = new CountDownLatch(2); // 假设有两个子线程
new Thread(() -> {
    // 子线程1执行的代码
    latch.countDown();
}).start();
new Thread(() -> {
    // 子线程2执行的代码
    latch.countDown();
}).start();
try {
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

方法三:使用线程池

如果使用线程池来管理子线程,可以通过主线程等待线程池中的任务执行完毕来实现主线程等待子线程结束。

示例代码:


ExecutorService executorService = Executors.newFixedThreadPool(2); // 假设线程池大小为2
executorService.submit(() -> {
    // 子线程1执行的代码
});
executorService.submit(() -> {
    // 子线程2执行的代码
});
executorService.shutdown();
try {
    executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
    e.printStackTrace();
}

结语

以上介绍了三种实现主线程等待子线程结束的方法:使用Thread的join方法、使用CountDownLatch类、使用线程池。根据实际需求选择合适的方法可以帮助我们更好地管理线程,提高程序的效率和稳定性。

谢谢您阅读本文,希望对您在Java多线程编程中有所帮助!

三、tomcat线程会自动回收吗?

线程是自动调整的,不需要你来手工调整。 优化的文档可以参考edocs里相关的内容。 在控制台上,队列设置里面,有Thread Count、Thread Priority和Queue Length,可以调整一下。另外,backlog也可以调整一下。 提升线程数会在重新启动server后生效,队列长度是执行队列的最大长度,提升线程优先权将导致这个队列cpu优先权超过低优先权线程。 提升线程数有时可以增加应用程序性能,但是在提升数量前,有很多因素需要考虑。设置数量太高会降低服务器性能,在服务器上可以并行处理的线程数取决于服务器硬件的cpu性能,有效的处理器越多,可以给服务器的线程数越多,并且越期望有性能的提高。 你也可以在config.xml中加入8.1 style,用81-style-execute-queues ,配置一下 myserver myserver true 7002 true

四、子线程如何获得主线程的handler?

是可以访问的!但是,你不可以在非UI线程(子线程)创建handler。所以Handler的创建应该放在UI线程(主线程),然后在非UI线程(子线程)中使用它。

例如,你可以在UI线程(主线程)中创建:Handler handler = new Handler(); 然后在非UI线程(子线程)中使用:handler.sendEmptyMessage(0)

;这样你就可以通过多线程来处理android的UI,这也是几种异步处理UI方式中的一种。希望对你有帮助哦!

五、子线程可以直接调用主线程方法吗?

启动线程的时候有个参数LPVOID ,可以通过此参数把主线程的中的对象指针传递进去,在子线程中用这个指针来调用它的成员函数。但要注意的是,不要在子线程中直接用指针调用主线程中的窗口对象的成员函数。这样会引发一些潜在错误。因为MFC不是线程安全的。

六、c语言主线程如何终止子线程?

有三种方式可以终止线程,具体调用函数依赖于使用的线程系统。

1 在线程入口函数中,调用return。 即退出线程入口函数,可以实现终止当前线程效果;

2 在线程执行的任意函数,调用当前线程退出函数,可以退出当前线程;

3 在任意位置,调用线程终止函数,并传入要终止线程的标识符,即pid,可以实现终止对应线程效果。

七、linux 线程阻塞就是不分配cpu资源给线程吗?

在Linux中,线程阻塞是指线程暂时停止执行,不占用CPU资源。当线程遇到阻塞操作(如等待I/O完成、等待锁、等待信号等)时,它会进入阻塞状态,将CPU资源让给其他可执行的线程。在阻塞状态下,线程不会被调度执行,直到阻塞条件满足后才会被唤醒并重新调度执行。

这种机制可以提高系统的并发性和资源利用率,确保CPU资源被合理分配给其他可执行的线程,从而提高系统的性能和响应能力。

八、python中主线程怎样捕获子线程的异常?

创建线程的时候将线程设置为后台线程。设置支线程的IsBackground为true即可

九、c语言主线程和子线程的通信方法?

在C语言中,主线程和子线程之间可以通过共享内存、全局变量、信号量、互斥锁、条件变量等方式进行通信。

共享内存是最常用的方法,主线程和子线程可以通过读写共享内存来交换数据。

全局变量也可以用于通信,主线程和子线程可以通过读写全局变量来传递信息。

信号量可以用于同步和互斥,主线程和子线程可以通过信号量来控制对共享资源的访问。

互斥锁可以用于保护临界区,主线程和子线程可以通过互斥锁来实现互斥访问。

条件变量可以用于线程间的等待和通知,主线程和子线程可以通过条件变量来实现等待和唤醒操作。以上这些方法都可以实现主线程和子线程之间的通信。

十、资源回收怎么入门?

资源回收入门,可以从了解基本的回收知识开始,包括不同材料的回收标准、可回收垃圾的分类方法、不可回收垃圾的处理方式等。

其次,要建立良好的回收习惯,将可回收垃圾正确分类并定期交付回收公司,避免有害物质污染环境;

此外,要建立个人回收记录,以证明自己的可持续发展行为。