如何在Android中實現非同步任務
在Android應用程式中,當我們需要與可能需要時間的外部資源(例如從外部API或資料庫獲取資料)進行互動時,我們希望主UI保持互動並阻止UI執行緒在長時間執行的程序中執行很活躍。 另請注意,預設情況下,不允許在Android的UI執行緒中執行網路任務。 如果主執行緒用
在Android應用程式中,當我們需要與可能需要時間的外部資源(例如從外部API或資料庫獲取資料)進行互動時,我們希望主UI保持互動並阻止UI執行緒在長時間執行的程序中執行很活躍。另請注意,預設情況下,不允許在Android的UI執行緒中執行網路任務。
如果主執行緒用於獲取外部資料,則在獲取資料時主UI將不會保持互動,並且如果資料獲取過程遇到異常,則可能顯示異常行為。在這種情況下,android的非同步任務變得很方便,尤其是使用後臺執行緒更新UI的一部分。
非同步任務是將主執行緒的工作解除安裝到某個後臺執行緒的幾種方法之一。雖然AsyncTask不是唯一的選擇,但這是一個簡單而且非常常見的選擇。
在開始之前,我想訪問谷歌的開發者頁面包含的資訊AsyncTask在https://developer.android.com/reference/android/os/AsyncTask 審議有關實施一些內容AsyncTask秒。
這個AsyncTask班是一個abstract班級。實現通常是在UI執行緒上執行的類的子類。(AsyncTask即子類)的實現將覆蓋至少一種方法,通常是兩種方法。
執行非同步任務時,任務將執行4個步驟,如Android開發人員頁面中所述,網址為https://developer.android.com/reference/android/os/AsyncTask :
onPreExecute,在執行任務之前在UI執行緒上呼叫。此步驟用於設定任務,例如通過在使用者介面中顯示微調器。
doInBackground(Params...,在完成執行後立即在後臺執行緒上呼叫。此步驟用於執行可能需要很長時間的後臺計算。非同步任務的引數將傳遞給此步驟。計算結果必須由此步驟返回,並將傳遞迴最後一步。此步驟還可用於釋出一個或多個進度單位。這些值釋出在UI執行緒中steponProgressUpdate(Progress...)
onProgressUpdate(Progress...,在呼叫publishProgress(Progress...步驟後在UI執行緒上呼叫。執行的時間是不確定的。此方法用於在後臺計算仍在執行時顯示使用者介面中的任何形式的進度。例如,它可用於為進度條設定動畫或在文字欄位中顯示日誌。
onPostExecute(Result),在後臺計算完成後在UI執行緒上呼叫。背景計算的結果作為引數傳遞給該步驟。
我將通過程式碼來說明工作機制。程式碼來自我為Udacity的Android奈米學位課程所做的頂點專案。完整程式碼可在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。設定涉及為回收器檢視等建立介面卡,最後呼叫a LoadView()。在LoadView()方法執行AsyncTask從網路獲取資料和更新檢視的介面卡。
在這樣做的過程中,我們建立了一個NetworkQueryTask從中擴充套件的子類AsyncTask。該類有三個引數string,Void 和ArrayList<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的實現。