android支持多线程吗?

一、android支持多线程吗?

android支持多线程。

因为android搭载了最先进的多线程系统和功能芯片系统,并且是可以进行无限距离操作的,所以是支持的。

二、android什么场景需要线程池?

android在要创建多个线程做同一件事情时场景需要线程池,线程池减少创建线程的开发

三、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成员的反射是有风险的,万一在某个版本中这个成员的名称换了,这种方法就会出错。

四、Android线程间通信有哪几种方式?

四种方式,如下:

1、handle机制

2、runOnUiThread方法

3、View.post(Runnable r)

4、AsyncTask

五、android主进程销毁了,线程会不会也销毁?

会。

Android基于Linux内核开发,其进程调度相关方面和Linux保持一致。主进程销毁后,其中的线程会被内核执行销毁流程,并回收资源,但是子进程并不会。因为线程使用的是主进程进程空间,进程空间销毁意味着线程随之销毁;子进程拥有独立进程空间,如果销毁父进程,子进程会被初始进程接管成为继父进程。

六、Android在子线程用handler发送的消息,主线程是怎么loop到的?

是可以访问的!

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

例如,你可以在UI线程(主线程)中创建:Handlerhandler=newHandler();

然后在非UI线程(子线程)中使用:handler.sendEmptyMessage(0);

这样你就可以通过多线程来处理android的UI,这也是几种异步处理UI方式中的一种。

希望对你有帮助哦!

七、如何在Java中唤醒睡眠中的线程

在Java编程中,多线程是一个常见的概念。在某些情况下,我们可能会让线程进入睡眠状态,即等待某个特定条件的发生。然而,一旦满足了该条件,我们需要唤醒这些睡眠中的线程,使其继续执行。

什么是线程睡眠?

首先,我们来看一下什么是线程睡眠。在线程中,睡眠是指让线程暂停执行一段时间,不参与CPU的竞争。当一个线程进入睡眠状态后,它暂时不会再次被调度,直到满足某种特定条件。

线程睡眠的使用场景

线程睡眠的使用场景有很多。比如:

  • 当一个线程需要等待某个资源的释放,才能继续执行时
  • 当一个线程需要等待某个条件的满足,才能继续执行时
  • 当一个线程需要遵循一定的执行顺序时

如何唤醒睡眠中的线程

当线程进入睡眠状态后,我们需要唤醒它以继续执行。Java提供了以下方法可以用来唤醒睡眠中的线程:

  • notify():唤醒单个睡眠中的线程,选择性地通知等待队列中的一个线程
  • notifyAll():唤醒所有睡眠中的线程,通知等待队列中的所有线程
  • interrupt():通过中断睡眠中的线程来唤醒它,抛出一个中断异常

唤醒线程的示例

下面是一个使用wait()和notify()方法唤醒线程的示例:

    
    class MyThread extends Thread {
      boolean waiting = true;

      public void run() {
        while (waiting) {
          synchronized (this) {
            try {
              wait();
            } catch (InterruptedException e) {
              // 处理中断异常
            }
          }
        }
        // 继续执行后续逻辑
      }

      public void stopWaiting() {
        synchronized (this) {
          waiting = false;
          notify();
        }
      }
    }

    public class Main {
      public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        try {
          Thread.sleep(2000); // 睡眠2秒钟
        } catch (InterruptedException e) {
          // 处理中断异常
        }
        thread.stopWaiting(); // 唤醒线程
      }
    }
    
  

总结

在Java中,通过睡眠线程可以实现让线程等待某个条件的满足。一旦条件满足,我们可以使用notify()、notifyAll()或中断线程来唤醒睡眠中的线程。这样可以更好地控制线程的执行顺序和依赖关系。

感谢您阅读本文,希望本文能帮助您在Java编程中正确地唤醒睡眠中的线程。

八、深度解析 Android 多线程闪退问题与解决方案

在开发 Android 应用时,许多开发者会遇到一个让人感到十分棘手的问题,那就是多线程闪退。作为一个开发者,我也曾经历过这个令我抓狂的时刻。今天,我想通过我的经验与大家分享一些常见的多线程闪退原因以及相应的解决方案,希望能帮助到正在奋斗于 Android 开发的你。

回想起我第一次接触多线程时,满心期待,因为我知道多线程可以提升应用的性能和用户体验。然而,现实是当我尝试在后台线程中处理网络请求时,应用却频繁崩溃。最开始我找不到原因,后来才发现多线程的问题其实大有奥妙。

为什么会出现闪退?

多线程闪退的原因有很多,以下是一些比较常见的:

  • UI 线程阻塞: 如果在主线程中执行了耗时操作,比如进行网络请求或者数据库查询,应用就有可能无响应,最终导致闪退。
  • 访问 UI 元素: 在子线程中直接更新 UI 会引发异常,Android 不允许这样做。这是因为 UI 元素只能在主线程中进行操作。
  • 线程错误管理: 如果没有正确处理线程的创建、启动和停止,可能会出现内存泄漏或资源竞争等问题,最终导致闪退。

如何解决多线程闪退问题?

针对以上几个问题,我也总结了一些解决方案:

  • 使用 AsyncTask: AsyncTask 是 Android 提供的一个帮助类,能够简化后台任务的运行以及 UI 更新。尽管它在处理简单的后台任务方面非常有效,但要记住,AsyncTask 存在一些局限性,尤其是在处理复杂任务时。
  • Handler 和 Looper: 如果需要在子线程中与主线程进行通信,可以使用 Handler。通过它可以在子线程中发送消息到主线程,进而安全地更新 UI。
  • 使用 Kotlin Coroutines: 如果你在使用 Kotlin 开发 Android 应用,推荐使用协程来进行多线程操作。它不仅简化了异步代码,还能轻松管理线程的生命周期,大大降低了闪退的风险。

案例分析

举个例子,我曾经有个项目需要从服务器获取大量数据,我选择在子线程中执行网络请求,并试图直接在子线程中更新 RecyclerView 的适配器,这导致程序闪退。当我意识到这个问题后,改用 Handler 在主线程中进行 UI 更新,终于解决了闪退问题,应用的稳定性得到了明显提高。

总结与展望

多线程编程虽然复杂,但掌握一些基本的原则和最佳实践后,你会发现它的魅力所在。通过适当的线程管理,避免常见的错误,相信每个开发者都能写出流畅而稳定的 Android 应用。

在未来,我还希望看到 Android 平台能够提供更多便捷的工具来帮助开发者轻松应对多线程问题。对于我们来说,不断学习和实践才是克服一切技术难题的最好方式。

九、线程池,怎么停止线程池中的线程?

1.自然终止。

你的线程执行完它启动时运行的方法,就自然终止了。适用于单个或多个一次性任务。如果是多个,这种情况推荐线程池。把任务写成线程池的Task。

2.有个管理线程,去调用工作线程的workingThread.Interrupt()方法。前提是,用一些同步机制防止工作线程的工作做一半被扔那了。

这种办法适用于循环等待某些任务的线程,比如TCPListener的循环Accept的线程或者其它响应消息的线程,或者接收/处理心跳消息的线程。

十、四核八线程和六核十二线程区别在哪?

4核8线程属于2-3年内大多数用途都能满足。

6核12线程属于4-6年内大多数用途都能满足。

8核16线程属于6-10年内大多数用途都能满足

毕竟大多数人一台台式机不可能2年就淘汰或者更新CPU。

所以推荐不缺那400块的话,6核12线程起步。