Android多线程开启如何选择

目录

前言

AsyncTask

分析

总结

HandlerThread推荐

分析

总结

Service和IntentService

分析

总结

RxJava/RxAndroid

分析

总结

Kotlin协程推荐

分析

总结

Executor推荐

分析

总结


前言

     线程开启方式取决于你的应用程序需求和场景。在Android开发中,有几种常用的线程开启方式

AsyncTask

分析

    印象里面很多人说AsyncTask是有内存泄漏的,静态内部类没有回收,导致内存泄漏,导致很多人说这个AsyncTask设计有缺陷,都不敢用。

  实际上,AsyncTask内部静态类不会导致内存泄漏。因为静态内部类不持有对外部类的引用,它与外部类没有直接的联系。这就意味着静态内部类的实例在没有其他引用时,会随着垃圾回收而被释放,不会造成内存泄漏。就算是使⽤AsyncTask,只要任务的时间不⻓(例如10秒之内),那就完全没必要做防⽌内存泄露的处理。

  让我们来演示一个不会导致内存泄漏的AsyncTask内部静态类:

public class MyActivity extends AppCompatActivity {

    private static class MyAsyncTask extends AsyncTask {

        private WeakReference activityRef;

        MyAsyncTask(MyActivity activity) {
            activityRef = new WeakReference<>(activity);
        }

        @Override
        protected Void doInBackground(Void... voids) {
            // 在后台执行一些任务
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MyActivity activity = activityRef.get();
            if (activity != null) {
                // 执行操作,确保 activity 不为空
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        // 执行 AsyncTask
        MyAsyncTask asyncTask = new MyAsyncTask(this);
        asyncTask.execute();
    }
}

总结

  • 优点:简单快速,适合较简单的后台任务和UI更新操作。
  • 缺点:在Android API 30中已被标记为过时,不再推荐使用。不适合长时间运行的任务和多个并发任务。

HandlerThread推荐

分析

HandlerThreadThread的子类,它封装了一个带有Looper的线程,可以方便地在该线程中执行任务。通过HandlerThread,你可以在后台线程执行循环任务,而且在任务执行完成后,线程也不会立即销毁,可以重复使用。

下面是一个简单的示例,演示如何使用HandlerThread开启线程并执行任务:

public class MyHandlerThread extends HandlerThread {

    private Handler handler;

    public MyHandlerThread(String name) {
        super(name);
    }

    public void postTask(Runnable task) {
        handler.post(task);
    }

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        handler = new Handler(getLooper());
    }
}

在上面的代码中,我们创建了一个名为MyHandlerThread的类,继承自HandlerThread。该类在构造函数中调用了父类的构造函数,并在onLooperPrepared()方法中初始化了一个Handler,用于处理消息和任务。

现在,我们可以在MyHandlerThread中执行后台任务:

// 在 Activity 或其他类中使用 MyHandlerThread
MyHandlerThread handlerThread = new MyHandlerThread("MyHandlerThread");
handlerThread.start(); // 启动线程

handlerThread.postTask(new Runnable() {
    @Override
    public void run() {
        // 这里执行后台任务
        // 注意:此处在 MyHandlerThread 的后台线程中运行

        // 模拟耗时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 任务完成后,可以继续在后台线程中执行其他任务
    }
});

在上面的示例中,我们创建了一个MyHandlerThread实例,并通过start()方法启动线程。然后,通过postTask()方法将后台任务发送给HandlerThread中的Handler处理。Runnable中的任务将在MyHandlerThread的后台线程中执行。

使用HandlerThread可以方便地在后台线程执行循环任务,因为HandlerThread具有Looper,所以可以执行线程的消息循环。在不需要使用HandlerThread时,可以通过调用quit()方法停止线程,并释放相关资源。

总结

  • 优点:适用于需要长时间运行的后台任务,不需要单独的Service。内部实现了Looper和Handler,方便消息处理。
  • 缺点:较为底层,需要手动管理消息和线程间通信,复杂任务可能需要更多的代码。

Service和IntentService

分析

IntentService是一个特殊的Service类,用于执行后台任务。IntentService会自动创建一个工作线程来处理接收到的Intent,并在任务完成后自动停止。这使得在IntentService中执行耗时任务变得简单,并且不需要手动管理线程的生命周期。

IntentService中,你可以在onHandleIntent(Intent intent)方法中执行后台任务。该方法在工作线程中运行,所以你可以在其中执行长时间运行的操作,而不会阻塞主线程。

下面是一个简单的示例,演示如何在IntentService中开启线程并执行后台任务:

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 这里执行后台任务
        // 注意:此方法在工作线程中运行,不在主线程

        String data = intent.getStringExtra("data");
        Log.d("MyIntentService", "Received data: " + data);

        // 模拟耗时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 后台任务完成后,IntentService会自动停止
    }
}

在上面的示例中,我们创建了一个名为MyIntentServiceIntentService子类,并在onHandleIntent()方法中执行后台任务。这里简单地模拟了一个耗时操作,然后在后台任务完成后,IntentService会自动停止。

要启动IntentService并执行后台任务,你可以通过创建并发送一个Intent来完成:

Intent intent = new Intent(context, MyIntentService.class);
intent.putExtra("data", "Some data to process");
context.startService(intent);

当调用startService(intent)时,IntentService会创建一个工作线程并调用onHandleIntent()方法来执行后台任务。 

IntentService是一个方便的用于在后台执行任务的类,它自动管理线程和服务生命周期,并且在任务完成后自动停止。这使得在IntentService中执行耗时操作变得简单,并且不需要担心手动管理线程。

总结

  • 优点:适用于执行单次性任务或一次性请求,任务完成后自动停止。
  • 缺点:不适合长时间运行的任务,任务完成后服务会自动停止,不能进行长期后台处理。
  • Service:后台任务的活动空间。适⽤场景:⾳乐播放器等。

RxJava/RxAndroid

分析

在 RxJava 和 RxAndroid 中,你可以使用不同的操作符来开启和切换线程。RxJava 提供了多个线程调度器(Scheduler),用于控制观察者的执行线程和订阅线程。RxAndroid 是 RxJava 的 Android 扩展库,提供了与 Android 主线程(UI 线程)交互的调度器。

下面是一些常用的 RxJava/RxAndroid 线程调度器操作符:

  1. observeOn():用于指定观察者(Subscriber)执行的线程,即数据消费的线程。你可以使用 observeOn() 将观察者的执行切换到特定的线程,比如主线程、IO 线程等。

  2. subscribeOn():用于指定被观察者(Observable)执行的线程,即数据产生的线程。使用 subscribeOn() 可以将被观察者的执行切换到特定的线程。

在 RxJava/RxAndroid 中,常见的线程调度器有:

  • Schedulers.io():适合执行 I/O 操作,如网络请求、文件读写等。它使用的是一个线程池,适合执行较长时间的任务。

  • Schedulers.computation():适合执行计算密集型操作,如数据处理、数学计算等。它使用的是一个线程池,适合执行耗时较短的任务。

  • Schedulers.newThread():每次都会创建一个新的线程。不适合执行大量并发的任务。

  • AndroidSchedulers.mainThread()(RxAndroid):用于将观察者的执行切换到 Android 主线程(UI 线程),以便更新 UI。

下面是一个简单的示例,演示如何使用 RxJava/RxAndroid 来开启和切换线程:

Observable.create(new ObservableOnSubscribe() {
    @Override
    public void subscribe(ObservableEmitter emitter) throws Exception {
        // 在 IO 线程执行耗时操作,比如网络请求等
        int result = doNetworkRequest();
        emitter.onNext(result);
        emitter.onComplete();
    }
})
.subscribeOn(Schedulers.io()) // 指定被观察者在 IO 线程执行
.observeOn(AndroidSchedulers.mainThread()) // 指定观察者在主线程执行
.subscribe(new Observer() {
    @Override
    public void onSubscribe(Disposable d) {
        // 订阅时的处理
    }

    @Override
    public void onNext(Integer result) {
        // 在主线程中更新 UI
        updateUI(result);
    }

    @Override
    public void onError(Throwable e) {
        // 错误处理
    }

    @Override
    public void onComplete() {
        // 完成时的处理
    }
});

在这个示例中,我们使用 subscribeOn(Schedulers.io()) 将被观察者的执行切换到 IO 线程,执行网络请求等耗时操作。然后使用 observeOn(AndroidSchedulers.mainThread()) 将观察者的执行切换到 Android 主线程,以便在 onNext() 方法中更新 UI。

这样就能在 RxJava/RxAndroid 中有效地控制线程的切换和执行。记得在使用 RxJava/RxAndroid 时,根据具体场景合理选择适当的线程调度器,以避免性能问题和线程安全问题。

总结

  • 优点:强大的响应式编程库,可以处理异步任务和事件流,具有丰富的操作符和线程切换功能。
  • 缺点:学习曲线较陡峭,对初学者可能有一定的学习成本。

Kotlin协程推荐

分析

在 Kotlin 中,协程(Coroutines)是一种轻量级的并发编程工具,可以简化异步任务的处理和管理。使用协程可以避免回调地狱和手动管理线程的复杂性,使异步代码更易读和维护。

在 Kotlin 中,你可以使用launch函数和async函数来启动协程,并指定运行协程的上下文(Context)来决定在哪个线程中运行协程。

下面是一个示例,演示如何使用 Kotlin 协程启动线程并执行后台任务:

import kotlinx.coroutines.*

fun main() {
    // 启动一个协程在后台执行任务
    val job = GlobalScope.launch(Dispatchers.IO) {
        // 在后台执行一些任务
        delay(3000) // 模拟耗时操作
        println("Task completed!")
    }

    // 等待协程执行完成
    runBlocking {
        job.join()
    }
}

在上面的代码中,我们使用launch(Dispatchers.IO)来启动一个协程,并指定了Dispatchers.IO上下文,表示该协程将在 IO 线程池中运行,适合执行 I/O 相关的操作。然后,我们在协程中使用delay(3000)来模拟一个耗时操作。

runBlocking函数是一个阻塞的函数,它会等待所有内部协程执行完成。在这个示例中,我们使用runBlocking函数来等待协程执行完成,从而保证在协程完成后程序不会立即退出。

除了GlobalScope.launch,你还可以在其他地方启动协程,比如在 Android 中,在 ViewModel 或者其他组件中启动协程。要使用协程,你需要在项目中添加 Kotlin 协程库的依赖。

请注意,在 Android 中,如果你使用了 Kotlin 协程库的 Android 扩展库,你可以使用Main上下文(Dispatchers.Main)来在主线程中运行协程,以便更新 UI。

Kotlin 协程是一个强大的并发编程工具,可以简化异步任务的处理。通过使用launchasync函数,并指定适当的上下文,可以方便地在 Kotlin 中开启线程并执行后台任务。

总结

  • 优点:轻量级的并发编程库,简化异步任务的处理和管理,支持结构化并发,代码更加简洁易读。
  • 缺点:需要在Kotlin项目中使用,对于Java项目需要引入Kotlin。

Executor推荐

分析

在 Android 中,可以通过Executor接口和相关的实现类来开启线程并执行后台任务。通常,我们使用ThreadPoolExecutorExecutors类来创建和管理线程池,以便更好地控制线程的并发度和复用。

下面是一个简单的示例,演示如何在 Android 中使用Executor开启线程并执行后台任务:

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class MyTask implements Runnable {
    @Override
    public void run() {
        // 在后台执行一些任务
        // 注意:此处在后台线程中运行

        // 模拟耗时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 任务完成后,可以继续在后台线程中执行其他任务
    }
}

// 在 Activity 或其他类中使用 Executor
Executor executor = Executors.newSingleThreadExecutor(); // 创建单个后台线程的线程池

executor.execute(new MyTask()); // 提交任务给线程池执行

在上面的示例中,我们创建了一个MyTask类实现了Runnable接口,它用于执行后台任务。然后,我们通过Executors.newSingleThreadExecutor()创建一个单个后台线程的线程池,并使用execute()方法将MyTask任务提交给线程池执行。

这样就可以在 Android 中使用Executor开启线程并执行后台任务。根据具体的场景和需求,你还可以选择不同类型的线程池来管理并发任务,比如Executors.newFixedThreadPool()用于创建固定大小的线程池,Executors.newCachedThreadPool()用于创建无限大小的线程池等。使用合适的线程池可以有效地管理线程资源,避免资源浪费和性能问题。

总结

总体而言,在 Android 开发中使用Executor来管理线程池是非常常见和有效的做法。合理地配置和使用线程池可以提高应用程序的性能和稳定性,同时避免了手动管理线程的复杂性。但需要注意的是,在使用线程池时要注意资源消耗和内存泄漏问题,以确保应用程序的健壮性。

你可能感兴趣的:(原创,android)