在 Android 中实现异步任务






4.89/5 (2投票s)
如何在 Android 中实现异步任务
引言
在 Android 应用中,当我们需要与可能耗时的外部资源进行交互时,例如从外部 API 或数据库获取数据,我们希望主 UI 保持交互性,并在长时间运行的进程活动期间阻止 UI 线程运行。还要注意,默认情况下,在 Android 中不允许在 UI 线程中运行网络任务。
如果使用主线程获取外部数据,那么在获取数据期间主 UI 将不会保持交互性,并且在数据获取过程中遇到异常时可能会显示异常行为。在这种情况下,Android 的异步任务会很有用,特别是用于使用后台线程更新 UI 的一部分。
异步任务是将主线程的工作卸载到某个后台线程的几种方法之一。虽然 AsyncTask 不是唯一的选择,但它简单且相当常见。
为了开始,我想访问 Google 的开发者页面,其中包含有关 AsyncTask 的信息,网址为 https://developer.android.com.cn/reference/android/os/AsyncTask,以回顾一些与实现 AsyncTask 相关的内容。
AsyncTask 类是一个 abstract 类。实现通常是运行在 UI 线程上的类的子类。AsyncTask 的实现,即子类,将至少覆盖一个方法,通常是两个方法。
当执行异步任务时,任务会经历 4 个步骤,如 Android 开发者页面 https://developer.android.com.cn/reference/android/os/AsyncTask 所述。
- onPreExecute,在任务执行前在 UI 线程上调用。此步骤用于设置任务,例如通过在用户界面中显示加载指示器。
- doInBackground(Params...,在任务执行完毕后立即在后台线程上调用。此步骤用于执行可能耗时的后台计算。异步任务的参数会传递到此步骤。计算结果必须由此步骤返回,并会传递回最后一个步骤。此步骤还可以用于发布一个或多个进度单位。这些值会在 UI 线程上发布,在- steponProgressUpdate(Progress...)中。
- onProgressUpdate(Progress...,在调用- publishProgress(Progress... 步骤后,在 UI 线程上调用。执行时间不确定。此方法用于在后台计算仍在执行时在用户界面中显示任何形式的进度。例如,它可以用于动画进度条或在文本字段中显示日志。
- onPostExecute(Result),在后台计算完成后在 UI 线程上调用。后台计算的结果作为参数传递到此步骤。
我将回顾代码以说明工作机制。代码来自我在 Udacity 的 Android Nano-degree Program 的毕业设计项目。完整代码可在 https://github.com/benktesh/Capstone-Project 获取。在此演示中,我使用了下面代码块中所示的轻量级代码版本。
package benktesh.smartstock;
import android.app.ActivityOptions;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.util.ArrayList;
import benktesh.smartstock.Model.Stock;
import benktesh.smartstock.UI.CommonUIHelper;
import benktesh.smartstock.UI.StockDetailActivity;
import benktesh.smartstock.Utils.MarketAdapter;
import benktesh.smartstock.Utils.NetworkUtilities;
import benktesh.smartstock.Utils.PortfolioAdapter;
import benktesh.smartstock.Utils.SmartStockConstant;
public class MainActivity extends AppCompatActivity implements
        MarketAdapter.ListItemClickListener, PortfolioAdapter.ListItemClickListener {
    private static final String TAG = MainActivity.class.getSimpleName();
    CommonUIHelper mCommonUIHelper;
    ArrayList<Stock> mMarketData;
    private Toast mToast;
    //The following are for market summary
    private MarketAdapter mAdapter;
    private RecyclerView mMarketRV;
    private ProgressBar spinner;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        spinner = findViewById(R.id.progressbar);
        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent Email = new Intent(Intent.ACTION_SEND);
                Email.setType(getString(R.string.label_emailtype));
                Email.putExtra(Intent.EXTRA_EMAIL,
                        new String[]{getString
                           (R.string.label_developer_contat_email)});  //developer 's email
                Email.putExtra(Intent.EXTRA_SUBJECT,
                        R.string.label_feedback_subject); // Email 's Subject
                Email.putExtra(Intent.EXTRA_TEXT, 
                 getString(R.string.label_address_developer) + "");  //Email 's Greeting text
                startActivity(Intent.createChooser(Email, getString(R.string.label_send_feedback)));
            }
        });
        if (mCommonUIHelper == null) {
            mCommonUIHelper = new CommonUIHelper(this);
        }
        mMarketRV = findViewById(R.id.rv_market_summary);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        mMarketRV.setLayoutManager(layoutManager);
        mMarketRV.setHasFixedSize(true);
        mAdapter = new MarketAdapter(mMarketData, this);
        mMarketRV.setAdapter(mAdapter);
        LoadView();
    }
    private void LoadView() {
        Log.d(TAG, "Getting Market Data Async");
        new NetworkQueryTask().execute(SmartStockConstant.QueryMarket);
    }
    private void MakeToast(String msg) {
        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
    }
    public boolean onCreateOptionsMenu(Menu menu) {
        return mCommonUIHelper.ConfigureSearchFromMenu(menu);
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        if (mCommonUIHelper.MakeMenu(item)) return true;
        return super.onOptionsItemSelected(item);
    }
    @Override
    public void onListItemClick(Stock data) {
        if (mToast != null) {
            mToast.cancel();
        }
        Intent intent = new Intent(this.getApplicationContext(), StockDetailActivity.class);
        intent.putExtra(SmartStockConstant.ParcelableStock, data);
        Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
        startActivity(intent, bundle);
    }
    /*
    This is an async task that fetches data from network and new data is applied to adapter.
    Also makes a long toast message when fails to retrieve information from the network
    It takes void, void and returns ArrayList<?>
     */
    class NetworkQueryTask extends AsyncTask<String, Integer, ArrayList<Stock>> {
        private String query;
        @Override
        protected void onPreExecute() {
            if (spinner != null) {
                spinner.setVisibility(View.VISIBLE);
            }
        }
        @Override
        protected ArrayList<Stock> doInBackground(String... params) {
            query = params[0];
            ArrayList<Stock> searchResults = null;
            try {
                searchResults = NetworkUtilities.getStockData(getApplicationContext(), query);
                for (int i = 0; i <= 100; i = i + 25) {
                    Thread.sleep(500);
                    publishProgress(i);
                }
            } catch (Exception e) {
                Log.e(TAG, e.toString());
            }
            return searchResults;
        }
        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            Toast.makeText(getApplicationContext(), "Progress:  " + 
                           progress[0] + "(%)", Toast.LENGTH_SHORT).show();
        }
        @Override
        protected void onPostExecute(ArrayList<Stock> searchResults) {
            super.onPostExecute(searchResults);
            if (searchResults != null && searchResults.size() != 0) {
                mAdapter.resetData(searchResults);
            }
            if (spinner.getVisibility() == View.VISIBLE) {
                spinner.setVisibility(View.GONE);
            }
        }
    }
}
类 NetworkQueryTask 实现为 MainActivity 的子类,并且该子类扩展了 Android 的 AsyncTask abstract 类。子类可以定义如下:
     private class NetworkQueryTask extends AsyncTask<T1, T2, T3> {...}
T1、T2 和 T3 是参数的数据类型,并且它们各自具有特定含义。
上述任务可以如下执行:
     new NetworkAsyncTask().execute(param1);
param1 的类型与 T1 的类型相同。
MainActivity 在 UI 线程上运行。'onCreate(..)' 方法负责设置 UI。设置包括创建 RecyclerView 的适配器等,最后调用 LoadView()。LoadView() 方法执行 AsyncTask 以从网络获取数据并更新视图的适配器。
为此,我们创建了一个名为 NetworkQueryTask 的子类,该子类继承自 AsyncTask。该类接受三个参数:string、Void 和 ArrayList<Stock>。Stock 是一个简单的类,用于存储有关 Stock 的信息。一旦进程开始,我们希望加载指示器可见,这可以在 doInBackground(..) 方法中完成。
在上述任务中,三个参数分别表示用于 doInBackground(T1 param1)、onProgressUpdate(T2 param2) 和 onPostExecute(T3 param3) 的输入参数类型。当 doInBackground) 步骤完成执行时,param3 将成为 doInBackground 步骤的输出,并成为 onPostExecute(param3) 方法的输入。
子类通常至少覆盖一个方法,最常见的是 doInBackground(..) 方法,以及第二个方法,即 onPostExecute()。onProgressUpdate 和 onPreExecute() 方法是可选的,可以省略。因此,如果没有关于进度更新需要执行的操作,那么就不需要覆盖 onProgressUpdate,并且在类定义本身中 param2 可以是 Void 类型。例如,假设需要将一个 string 参数传递给 doInBackground(),并且不需要 onProgressUpdate() 方法,而 onPostExecute() 方法接受一个 string 参数,那么类定义将如下所示:
    private class NetworkQueryTask extends AsyncTask<String, Void, String> {...}
因此,我们可以说这三个参数分别代表 doInBackground 的输入、onProgressUpdate() 的输入和 onPostExecute 的输入。doInBackground 输出的参数类型与 onPostExectute() 的输入相同。另外,如果 async 任务是“即发即弃”的,例如触发某些操作,那么所有参数都可以是 void。例如,在这种情况下,子类的定义将如下所示:
     private class NetworkQueryTask extends AsyncTask<Void, Void, Void> {...}
并且上面的类如下执行:
     new NetworkAsyncTask().execute();
AsyncTasks 不知道应用中的其他活动,因此在活动被销毁时必须正确处理。因此,AsycnTask 不适合长时间运行的操作,因为如果应用在后台,并且 Android 终止了调用 AsyncTask 的应用,AsyncTask 不会被杀死,我们必须管理 AsyncTask 结果的处理过程。因此,AsyncTasks 在获取非长时间运行的数据方面很有用。除了 AyscTask 之外,还有其他替代方案,例如 IntentServices、Loader 和 JobScheduler 以及许多基于 Java 的实现。
带有代码演示的 YouTube 视频可在 https://youtu.be/Yxg_janIavw 找到。

