Android 11中已经弃用了AsyncTask API,有哪些替代方案?

231

谷歌正在逐步淘汰Android AsyncTask API,建议在Android 11中使用java.util.concurrent代替。您可以查看此提交

 *
 * @deprecated Use the standard <code>java.util.concurrent</code> or
 *   <a href="https://developer.android.com/topic/libraries/architecture/coroutines">
 *   Kotlin concurrency utilities</a> instead.
 */
@Deprecated
public abstract class AsyncTask<Params, Progress, Result> {
如果您正在维护一个在Android中具有异步任务的旧代码库,那么未来可能需要更改它。 我的问题是:使用java.util.concurrent,以下代码片段的适当替换应该是什么? 它是Activity的静态内部类。 我正在寻找适用于minSdkVersion 16的解决方案。

如果您正在维护一个在Android中具有异步任务的旧代码库,那么未来可能需要更改它。 我的问题是:使用java.util.concurrent,以下代码片段的适当替换应该是什么? 它是Activity的静态内部类。 我正在寻找适用于minSdkVersion 16的解决方案。

private static class LongRunningTask extends AsyncTask<String, Void, MyPojo> {
        private static final String TAG = MyActivity.LongRunningTask.class.getSimpleName();
        private WeakReference<MyActivity> activityReference;

        LongRunningTask(MyActivity context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected MyPojo doInBackground(String... params) {
            // Some long running task
            
        }

        @Override
        protected void onPostExecute(MyPojo data) {

            MyActivity activity = activityReference.get();
            activity.progressBar.setVisibility(View.GONE);
            populateData(activity, data) ;
        }     


    }

45
“被弃用”意味着Google建议您转移到其他东西。这并不意味着该类很快就会被删除。特别地,AsyncTask不能被删除,否则会破坏向后兼容性。 - CommonsWare
11
@Style-7 不是。 - EpicPandaForce
28
这太糟糕了。建议使用来自官方Android文档的“AsyncTask”。我之前是后端开发人员,已经熟悉了executorService。基于这个建议,我将所有后台任务迁移到了使用“AsyncTask”。现在他们告诉我们不要再使用它了? - Kimi Chiu
1
@CommonsWare 从Android 11开始,Google开始删除已弃用的方法。 - Duna
3
@Addy:我在这里评论的具体问题是AsyncTask删除,这样做会破坏许多现有的应用程序。程序员应该学习除了AsyncTask之外的其他技术(如RxJava、Kotlin协程等),因为它们更好,并且在专业环境中使用得更广泛。 - CommonsWare
显示剩余8条评论
20个回答

196

你可以直接使用Java并发包中的Executors

我也搜索了相关信息,在这篇Android Async API已弃用的文章中找到了解决方案。

不幸的是,这篇文章使用的是Kotlin语言,但是经过一些努力,我已将其转换为Java语言。因此,以下是解决方案。

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(new Runnable() {
    @Override
    public void run() {

        //Background work here

        handler.post(new Runnable() {
            @Override
            public void run() {
                //UI Thread work here
            }
        });
    }
});

非常简单,对吧?如果你的项目使用Java 8,你甚至可以再简化它一点。

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
    //Background work here
    handler.post(() -> {
        //UI Thread work here
    });
});

然而,相对于代码的简洁性而言,它无法击败 Kotlin,但比之前的 Java 版本要好。

希望这可以帮助您。谢谢。


7
实际上,你可以再往下走一步:Executors.newSingleThreadExecutor().execute(() -> dao.insert(data)); - arun
2
谢谢您!这对我在调用服务器API时非常有帮助。但是,如果我想显示一些更新进度条,我应该把onProgressUpdate部分放在哪里? - Kroi
1
d = (◕‿↼) 很好的简短干净的答案,其中“干净”意味着没有关于“不要使用WeakReference<Context>而是去使用LiveData”之类的无意义话语。 - Top-Master
3
我不明白这种方法相比于正确使用AsyncTask有什么优势。你的方法和AsyncTask都使用了Java并发包中的类似组件,上面展示的代码没有做任何事情来防止在Activity或Fragment上下文中不正确使用AsyncTask时出现的“内存泄漏”问题。 - user1608385
进展更新怎么办?如何处理? - Prajwal Waingankar
显示剩余4条评论

125
private WeakReference<MyActivity> activityReference;

庆幸它已被弃用,因为WeakReference<Context>一直都是一种hack,而不是一个正确的解决方案

现在人们将有机会清理他们的代码。


AsyncTask<String, Void, MyPojo> 
基于此代码,实际上不需要 Progress,只有一个String输入和一个MyPojo输出。
这可以很容易地完成,而无需使用任何 AsyncTask。
public class TaskRunner {
    private final Executor executor = Executors.newSingleThreadExecutor(); // change according to your requirements
    private final Handler handler = new Handler(Looper.getMainLooper());

    public interface Callback<R> {
        void onComplete(R result);
    }

    public <R> void executeAsync(Callable<R> callable, Callback<R> callback) {
        executor.execute(() -> {
            final R result = callable.call();
            handler.post(() -> {
                callback.onComplete(result);
            });
        });
    }
}

如何传入字符串?像这样:

class LongRunningTask implements Callable<MyPojo> {
    private final String input;

    public LongRunningTask(String input) {
        this.input = input;
    }

    @Override
    public MyPojo call() {
        // Some long running task
        return myPojo;
    }
}

And

// in ViewModel
taskRunner.executeAsync(new LongRunningTask(input), (data) -> {
    // MyActivity activity = activityReference.get();
    // activity.progressBar.setVisibility(View.GONE);
    // populateData(activity, data) ;

    loadingLiveData.setValue(false);
    dataLiveData.setValue(data);
});

// in Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main_activity);

    viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    viewModel.loadingLiveData.observe(this, (loading) -> {
        if(loading) {
            progressBar.setVisibility(View.VISIBLE);
        } else {
            progressBar.setVisibility(View.GONE);
        }
    });

    viewModel.dataLiveData.observe(this, (data) -> {
        populateData(data);
    }); 
}

这个例子使用了单线程池,适用于数据库写入(或串行网络请求),但如果你想要用于数据库读取或多个请求,可以考虑以下执行器配置:

private static final Executor THREAD_POOL_EXECUTOR =
        new ThreadPoolExecutor(5, 128, 1,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

1
newSingleThreadExecutor 对于写操作更好,但是你应该在文章末尾一定要使用 THREAD_POOL_EXECUTOR 来进行数据库读取。 - EpicPandaForce
2
在 TaskRunner 中,我遇到了编译器错误“未处理的异常 java.lang.Exceptionatcallable.call()` ... 最好的处理方法是什么? - k2col
2
我非常欣赏这个例子。谢谢!最终我几乎完全按照你展示的代码示例使用了静态执行器,但仍然使用了Executors.newSingleThreadExecutor() - dontangg
2
如果您需要取消操作,则必须使用executor.submit并取消future,而不是使用executor.execute。https://www.baeldung.com/java-future - EpicPandaForce
1
我觉得这个解决方案就像是 AsyncTask 的简化版,因为 AsyncTask 在内部使用了 Executor 和 Handler,并提供了其他功能,如取消、更新进度等。 - Son Truong
显示剩余33条评论

59

最简单的替代方案之一是使用 Thread

new Thread(new Runnable() {
    @Override
    public void run() {
        // do your stuff
        runOnUiThread(new Runnable() {
            public void run() {
                // do onPostExecute stuff
            }
        });
    }
}).start();

如果您的项目支持JAVA 8,您可以使用lambda

new Thread(() -> {
    // do background stuff here
    runOnUiThread(()->{
        // OnPostExecute stuff here
    });
}).start();

2
如何在后台处理调用时显示百分比? - Ahamadullah Saikat
你需要使用 runOnUiThread 来更新进度条或者任何其他用于更新/显示任务完成百分比的机制。 - Mayank Kumar Chaudhari
25
这个解决方案有几个缺点。首先,线程保留了对活动的引用,这可能会泄露上下文并导致应用程序崩溃。其次,我们无法从片段中使用此解决方案。第三,我们无法更新后台任务的进度。第四,没有办法取消线程。最后,在应用程序中创建了很多样板代码。 - Son Truong
你的代码甚至无法编译...我认为其中有一些错别字,例如当你说 new Runnable({...}) 时,你应该是指 new Runnable(){...}。因为第一个像是调用构造函数并传递数组初始化器,会触发编译器错误。而第二个则是创建匿名内部类的正确方式。 - Adam Burley
@Son Truong 我有三个问题的解决方案。首先,可以使用WeakReference来防止泄漏。其次,您可以通过getActivity()在片段中绝对使用this。第四,您可以使用Executer和Future随时取消。 - Faisal Khan
3
@SonTruong,我对这些缺点有几个真正的问题。 1:如果未被明确传递,线程如何/为什么保留活动的引用?我理解runOnUiThread,但对于短期任务,这不应该是个问题,对吗?3:后台任务的进度是否可以通过runOnUiThread中的相应调用来处理,就像publishProgress/onProgressionUpdate一样?4:下降到AsyncTaskFutureTask代码,所有它所做的就是使用Thread.interrupt功能来创建cancel功能。采用这种方法也可能实现相同的效果,对吗? - Benjamin Basmaci

44
根据Android文档AsyncTask已在API 30级中被弃用,建议使用标准的java.util.concurrent或Kotlin并发工具代替。

使用后者可以轻松实现:

  1. Create generic extension function on CoroutineScope:

     fun <R> CoroutineScope.executeAsyncTask(
             onPreExecute: () -> Unit,
             doInBackground: () -> R,
             onPostExecute: (R) -> Unit
     ) = launch {
         onPreExecute() // runs in Main Thread
         val result = withContext(Dispatchers.IO) { 
             doInBackground() // runs in background thread without blocking the Main Thread
         }
         onPostExecute(result) // runs in Main Thread
     } 
    
  2. Use the function with any CoroutineScope which has Dispatchers.Main context:

    • In ViewModel:

      class MyViewModel : ViewModel() {
      
          fun someFun() {
              viewModelScope.executeAsyncTask(onPreExecute = {
                  // ... runs in Main Thread
              }, doInBackground = {
                  // ... runs in Worker(Background) Thread
                  "Result" // send data to "onPostExecute"
              }, onPostExecute = {
                  // runs in Main Thread
                  // ... here "it" is the data returned from "doInBackground"
              })
          }
      }
      
    • In Activity or Fragment:

      lifecycleScope.executeAsyncTask(onPreExecute = {
          // ... runs in Main Thread
      }, doInBackground = {
          // ... runs in Worker(Background) Thread
          "Result" // send data to "onPostExecute"
      }, onPostExecute = {
          // runs in Main Thread
          // ... here "it" is the data returned from "doInBackground"
      })
      

    To use viewModelScope or lifecycleScope add next line(s) to dependencies of the app's build.gradle file:

    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope
    

    At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha05"

更新:

同时,我们可以使用onProgressUpdate函数实现进度更新:

fun <P, R> CoroutineScope.executeAsyncTask(
        onPreExecute: () -> Unit,
        doInBackground: suspend (suspend (P) -> Unit) -> R,
        onPostExecute: (R) -> Unit,
        onProgressUpdate: (P) -> Unit
) = launch {
    onPreExecute()

    val result = withContext(Dispatchers.IO) {
        doInBackground {
            withContext(Dispatchers.Main) { onProgressUpdate(it) }
        }
    }
    onPostExecute(result)
}

使用任何带有 Dispatchers.Main 上下文的 CoroutineScopeviewModelScope/lifecycleScope,请参见上面的实现)我们可以这样调用它:

someScope.executeAsyncTask(
    onPreExecute = {
        // ... runs in Main Thread
    }, doInBackground = { publishProgress: suspend (progress: Int) -> Unit ->
        
        // ... runs in Background Thread

        // simulate progress update
        publishProgress(50) // call `publishProgress` to update progress, `onProgressUpdate` will be called
        delay(1000)
        publishProgress(100)

        
        "Result" // send data to "onPostExecute"
    }, onPostExecute = {
        // runs in Main Thread
        // ... here "it" is a data returned from "doInBackground"
    }, onProgressUpdate = {
        // runs in Main Thread
        // ... here "it" contains progress
    }
)

5
有Java版本的这个解决方案吗? - Adnan haider
@Adnanhaider 很抱歉,它不是。 - Sergio
@Undefinedfunction executeAsyncTask 是一个在 CoroutineScope 上的扩展函数,你可以创建自己的 CoroutineScope 并在其上调用 executeAsyncTask - Sergio
我试图在onPostExecute中更新UI,但是我得到了错误ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.这似乎不是在UI线程中运行。 - Undefined function
1
这个解决方案救了我们的一天!非常感谢,兄弟! - Fahad-Android
显示剩余4条评论

17

使用此类在后台线程中执行后台任务。这个类 适用于所有的Android API版本,包括Android 11。同时,这段代码与带有 doInBackgroundonPostExecute 方法的 AsyncTask 相同。

public abstract class BackgroundTask {

    private Activity activity;
    public BackgroundTask(Activity activity) {
        this.activity = activity;
    }

    private void startBackground() {
        new Thread(new Runnable() {
            public void run() {

                doInBackground();
                activity.runOnUiThread(new Runnable() {
                    public void run() {

                        onPostExecute();
                    }
                });
            }
        }).start();
    }
    public void execute(){
        startBackground();
    }

    public abstract void doInBackground();
    public abstract void onPostExecute();

}

复制上述类后,您可以使用以下方式:

new BackgroundTask(MainActivity.this) {
        @Override
        public void doInBackground() {

            //put you background code
            //same like doingBackground
            //Background Thread
        }

        @Override
        public void onPostExecute() {

            //hear is result part same
            //same like post execute
            //UI Thread(update your UI widget)
        }
    }.execute();

我正在尝试在我的应用程序中实现这段代码,但是post execute代码不会终止。它会一直运行,直到我关闭应用程序。有什么办法可以消除这个问题吗? - user3079559

10

Android 11废弃了AsyncTask API,旨在消除一些问题。

那现在怎么办?

  • 线程(Threads)
  • 执行器(Executors)
  • RxJava
  • Listenable Futures
  • 协程(Coroutines)

为什么使用协程?

协程是Kotlin中进行异步编程的方式。自Kotlin 1.3以来,编译器支持稳定,与kotlinx.coroutines库配套使用-

  • 结构化并发
  • 非阻塞、顺序执行的代码
  • 取消传播
  • 自然异常处理

我听说协程有“易入门”的特点。这是否意味着它容易被攻破? - Branddd
使用 Kotlin 协程的实现 https://dev59.com/0FMH5IYBdhLWcg3wqg9V#62748880 - Sergio

9

在这里,我使用协程创建了一个替代AsyncTask的解决方案,可以像AsyncTask一样使用,而不需要在项目中改变太多代码。

  1. Create a new Abstract class AsyncTaskCoroutine which takes input parameter and output parameter datatypes of-course these parameters are optional :)

     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.GlobalScope
     import kotlinx.coroutines.async
     import kotlinx.coroutines.launch
    
     abstract class AsyncTaskCoroutine<I, O> {
         var result: O? = null
         //private var result: O
         open fun onPreExecute() {}
    
         open fun onPostExecute(result: O?) {}
         abstract fun doInBackground(vararg params: I): O
    
         fun <T> execute(vararg input: I) {
             GlobalScope.launch(Dispatchers.Main) {
                 onPreExecute()
                 callAsync(*input)
             }
         }
    
         private suspend fun callAsync(vararg input: I) {
             GlobalScope.async(Dispatchers.IO) {
                 result = doInBackground(*input)
             }.await()
             GlobalScope.launch(Dispatchers.Main) {
    
                 onPostExecute(result)
    
    
             }
         }
     }
    

2. 在Activity中,现在可以像以前使用AsyncTask一样使用它。

 new AsyncTaskCoroutine() {
                @Override
                public Object doInBackground(Object[] params) {
                    return null;
                }
    
                @Override
                public void onPostExecute(@Nullable Object result) {
    
                }
    
                @Override
                public void onPreExecute() {
    
                }
            }.execute();
  1. InCase if you need to send pass params

      new AsyncTaskCoroutine<Integer, Boolean>() {
    
         @Override
         public Boolean doInBackground(Integer... params) {
             return null;
         }
    
         @Override
         public void onPostExecute(@Nullable Boolean result) {
    
         }
    
         @Override
         public void onPreExecute() {
    
         }
     }.execute();
    

11
请勿使用 Kotlin,请先使用 Java,然后可以考虑将 Kotlin 作为备选项供那些正在使用它的人使用。谢谢。 - Darksymphony
7
@Darksymphony 我完全不同意你的观点,就使用Java而言,这个问题已经过时了。如果你仍在为Android使用Java,那么你需要重新考虑你的选择。他提供了一个非常好的AsynTask替代方案。 - Pankaj Kumar
21
希望 Java 能在接下来 X 年内继续作为 Android 的基本语言。有些懒惰的人提出了 Kotlin,并强迫 Android 开发人员使用它的有趣命令 :) 或许有一天我会重新考虑。但只要我们有选择,我就会坚持使用 Java。 - Darksymphony
很好,这个在非上下文类中运行得非常好,但是你如何取消它或获取进度呢? - Undefined function
2
@Darksymphony Kotlin是卓越的编程语言,毫无疑问。你必须适应时代。 - BeLambda
显示剩余5条评论

5

我还创建了一种使用抽象类的替代AsyncTask的方法,它可以作为一个类直接复制。

/app/src/main/java/../AsyncTasks.java

public abstract class AsyncTasks {
    private final ExecutorService executors;

    public AsyncTasks() {
        this.executors = Executors.newSingleThreadExecutor();
    }

    private void startBackground() {
        onPreExecute();
        executors.execute(new Runnable() {
            @Override
            public void run() {
                doInBackground();
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        onPostExecute();
                    }
                });
            }
        });
    }

    public void execute() {
        startBackground();
    }

    public void shutdown() {
        executors.shutdown();
    }

    public boolean isShutdown() {
        return executors.isShutdown();
    }

    public abstract void onPreExecute();

    public abstract void doInBackground();

    public abstract void onPostExecute();
}

上述类的实现/使用

new AsyncTasks() {
     @Override
            public void onPreExecute() { 
             // before execution
      }

     @Override
            public void doInBackground() {
              // background task here 
            }

     @Override
            public void onPostExecute() { 
             // Ui task here
            }
     }.execute();

为什么你即使使用ExecutorService,仍然将你的解决方案命名为async task? - Islam Alshnawey
1
仅为初学者简化。 - Attaullah
我已经在“public void run()”下面添加了“Thread.currentThread().setName(threadName);”,并通过Execute方法传递了threadName。这非常完美地工作。 - Patrick
你的代码里还有一个问题:你是如何调用 Shutdown 方法的呢?是通过 "private AsyncTasks mTask;" -> "mTask = new AsyncTasks();" -> "mTask.execute();",然后紧接着 "mTask.shutdown();" 吗? - Patrick
你如何在这种情况下使用onProgressUpdate(自动切换到主线程,以便你可以在任务中更新用户界面)? - undefined
显示剩余4条评论

5
被接受的答案很好。但是... 我没有看到cancel()方法的实现。
因此,我实现了一个可以取消正在运行任务(模拟取消)的版本。 在任务中断的情况下,需要取消以避免运行postExecute()方法。
public abstract class AsyncTaskExecutor<Params> {
    public static final String TAG = "AsyncTaskRunner";

    private static final Executor THREAD_POOL_EXECUTOR =
            new ThreadPoolExecutor(5, 128, 1,
                    TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

    private final Handler mHandler = new Handler(Looper.getMainLooper());
    private boolean mIsInterrupted = false;

    protected void onPreExecute(){}
    protected abstract Void doInBackground(Params... params);
    protected void onPostExecute(){}
    protected void onCancelled() {}

    @SafeVarargs
    public final void executeAsync(Params... params) {
        THREAD_POOL_EXECUTOR.execute(() -> {
            try {
                checkInterrupted();
                mHandler.post(this::onPreExecute);

                checkInterrupted();
                doInBackground(params);

                checkInterrupted();
                mHandler.post(this::onPostExecute);
            } catch (InterruptedException ex) {
                mHandler.post(this::onCancelled);
            } catch (Exception ex) {
                Log.e(TAG, "executeAsync: " + ex.getMessage() + "\n" + Debug.getStackTrace(ex));
            }
        });
    }

    private void checkInterrupted() throws InterruptedException {
        if (isInterrupted()){
            throw new InterruptedException();
        }
    }

    public void cancel(boolean mayInterruptIfRunning){
        setInterrupted(mayInterruptIfRunning);
    }

    public boolean isInterrupted() {
        return mIsInterrupted;
    }

    public void setInterrupted(boolean interrupted) {
        mIsInterrupted = interrupted;
    }
}

这个类的使用示例:

public class MySearchTask extends AsyncTaskExecutor<String> {

    public MySearchTask(){
    }

    @Override
    protected Void doInBackground(String... params) {
        // Your long running task
        return null;
    }

    @Override
    protected void onPostExecute() {
        // update UI on task completed
    }

    @Override
    protected void onCancelled() {
        // update UI on task cancelled
    }
}

MySearchTask searchTask = new MySearchTask();
searchTask.executeAsync("Test");

2
你对 mIsInterrupted 的使用是线程不安全的。要么将它变为原子/易失性变量,要么使用它的方法必须同步。 - Mikhail Vasilyev
@Vitaly,你能否请添加onProgressUpdate的实现呢? - Bhavna

4

谷歌建议使用Java的并发框架或者Kotlin协程。但是Rxjava在灵活性和功能方面比Java并发更强大,因此获得了相当多的受欢迎度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接