編輯:關於Android編程
Handler mHadler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what == 1){ Bitmap bitmap = (Bitmap) msg.obj; //更新UI... } } }; private void download(){ new Thread(new Runnable() { @Override public void run() { // 這裡進行下載操作...獲得了圖片的bitmap //下載完後才,向主線程發送Message Message msg = Message.obtain(); msg.obj = bitmap; msg.what = 1;//區分哪一個線程發送的消息 mHadler.sendMessage(msg); } }).start(); }可以看到,每次要進行下載工作,我們就得先創建出Thread,然後在主線程中寫好handler,為了對這個過程進行封裝,Android提供了AsyncTask異步任務,AsyncTask對線程和handler進行了封裝,使得我們可以直接在AsyncTask中進行UI的更新操作,就好像是在子線程進行UI更新一樣。
public abstract class AsyncTask其中,三個泛型類型參數的含義如下: Params:開始異步任務執行時傳入的參數類型,即doInBackground()方法中的參數類型; Progress:異步任務執行過程中,返回下載進度值的類型,即在doInBackground中調用publishProgress()時傳入的參數類型; Result:異步任務執行完成後,返回的結果類型,即doInBackground()方法的返回值類型; 有了這三個參數類型之後,也就控制了這個AsyncTask子類各個階段的返回類型,如果有不同業務,我們就需要再另寫一個AsyncTask的子類進行處理。{ ... }
public class MyAsyncTask extends AsyncTask上面doInBackground()中獲取進度值時,我們只是為了做一個進度值更新調用的演示,實際項目文件下載中,我們可能會對拿到的輸入流進行處理,比如讀取輸入流將文件保存到本地,在讀取輸入流的時候,我們就可以獲取到已經讀取的輸入流大小作為進度值了,如下:{ private ProgressBar mPreogressBar;//進度條 private ImageView mImageView;//圖片顯示控件 public MyAsyncTask(ProgressBar pb,ImageView iv){ mPreogressBar = pb; mImageView = iv; } @Override protected void onPreExecute() { super.onPreExecute(); mPreogressBar.setVisibility(View.VISIBLE); } @Override protected Bitmap doInBackground(String... params) { String urlParams = params[0];//拿到execute()傳過來的圖片url Bitmap bitmap = null; URLConnection conn = null; InputStream is = null; try { URL url = new URL(urlParams); conn = url.openConnection(); is = conn.getInputStream(); //這裡只是為了演示更新進度的功能,實際的進度值需要在從輸入流中讀取時逐步獲取 for(int i = 0; i < 100; i++){ publishProgress(i); Thread.sleep(50);//為了看清效果,睡眠一段時間 } //將獲取到的輸入流轉成Bitmap BufferedInputStream bis = new BufferedInputStream(is); bitmap = BitmapFactory.decodeStream(bis); is.close(); bis.close(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return bitmap; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); mPreogressBar.setProgress(values[0]); } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); mPreogressBar.setVisibility(View.GONE); mImageView.setImageBitmap(bitmap); } }
//實際項目中如何獲取文件大小作為進度值及更新進度值 int totalSize = conn.getContentLength();//獲取文件總大小 int size = 0;//保存當前下載文件的大小,作為進度值 int count = 0; byte[] buffer = new byte[1024]; while((count = is.read(buffer)) != -1){ size += count;//獲取已下載的文件大小 //調用publishProgress更新進度,它內部會回調onProgressUpdate()方法 publishProgress(size,totalSize); Thread.sleep(100);//為了看清效果,睡眠一段時間 }在MainActivity中使用:
public class MainActivity extends AppCompatActivity { private ImageView mImageView; private ProgressBar mProgressBar; private static String URL = "http://c.hiphotos.baidu.com/baike/s%3D220/sign=86442af5a6c27d1ea1263cc62bd4adaf/42a98226cffc1e17d8f914604890f603738de919.jpg"; private MyAsyncTask asyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.image); mImageView = (ImageView) findViewById(R.id.id_image); mProgressBar = (ProgressBar) findViewById(R.id.pb); asyncTask = new MyAsyncTask(mProgressBar, mImageView); asyncTask.execute(URL);//將圖片url作為參數傳入到doInBackground()中 } }布局文件如下:
效果如下: 由於需要聯網,注意在AndroidManifest.xml中加入網絡訪問權限。android:padding="16dp" android:layout_width="match_parent" android:layout_height="match_parent"> android:id="@+id/id_image" android:layout_width="match_parent" android:layout_height="match_parent" /> android:id="@+id/pb" style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_centerInParent="true" android:layout_width="match_parent" android:layout_height="30dp" />
因此,在MainActivity中,我們就需要加入loadImage方法,如下:android:padding="16dp" android:layout_width="match_parent" android:layout_height="match_parent"> android:id="@+id/id_image" android:layout_width="match_parent" android:layout_height="match_parent" /> android:id="@+id/pb" style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_centerInParent="true" android:layout_width="match_parent" android:layout_height="30dp" />
public class MainActivity extends AppCompatActivity { private ImageView mImageView; private ProgressBar mProgressBar; private static String url = "http://c.hiphotos.baidu.com/baike/s%3D220/sign=86442af5a6c27d1ea1263cc62bd4adaf/42a98226cffc1e17d8f914604890f603738de919.jpg"; private MyAsyncTask asyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.image); mImageView = (ImageView) findViewById(R.id.id_image); mProgressBar = (ProgressBar) findViewById(R.id.pb); asyncTask = new MyAsyncTask(mProgressBar, mImageView); asyncTask.execute(url); } public void loadImage(View v){ asyncTask.execute(url); } }現象一:在loadImage()方法中,我們直接再次通過asyncTask.execute()執行加載。看看此時效果如何: onCreate中初始加載完一次圖片後,我們點擊“加載圖片”按鈕,此時程序直接崩潰了!這是因為,每一個new出的AsyncTask只能執行一次execute(),如果同一個AsyncTask多次執行execute()執行將會報錯。 現象二:我們來修改loadImage()方法,在該方法中,我們在打開自身MainActivity,使得多次初始化的時候進行加載,如下:
public void loadImage(View v){ Intent i = new Intent(this,MainActivity.class); startActivity(i); }此時效果如下: 在第一次運行程序進入MainActivity,執行execute但在顯示出圖片之前,立即點擊“加載圖片”按鈕,新打開一個MainActivity,我們發現這個MainActivity的進度條沒有立即展示出進度出來,說明這個MainActivity的AsyncTask沒有立即執行doInBackground(),這是因為AsyncTask內部使用的是線程池,相當於裡面有一個線程隊列,執行一次execute時會將一個下載任務加入到線程隊列,只有前一個任務完成了,下一個下載任務才會開始執行。 為了達到我們想要的效果,我們自然想到把上一個任務給取消掉。的確,AsyncTask為我們提供了cancel()方法來取消一個任務的執行,但是要注意的是,cancel方法並沒有能力真正去取消一個任務,其實只是設置這個任務的狀態為取消狀態,我們需要在doInBackground()下載中進行檢測,一旦檢測到該任務被用戶取消了,立即停止doInBackground()方法的執行。 我們先修改MainActivity,根據不同業務需求,在不同地方進行任務的取消,我們這裡在onPause()中進行任務的取消,在MainActivity方法中加入onPause()方法,如下:
@Override protected void onPause() { super.onPause(); if(asyncTask != null && asyncTask.getStatus() == AsyncTask.Status.RUNNING){ //cancel只是將對應的任務標記為取消狀態 asyncTask.cancel(true); } }繼續修改AsyncTask,在這裡面進行任務是否被取消的檢測,這裡我們只簡單修改下doInBackground()和onProgressUpdae()方法,實際項目中開自己的業務邏輯來控制,如下:
@Override protected Bitmap doInBackground(String... params) { String urlParams = params[0];//拿到execute()傳過來的圖片url Bitmap bitmap = null; URLConnection conn = null; InputStream is = null; try { URL url = new URL(urlParams); conn = url.openConnection(); is = conn.getInputStream(); for(int i = 0; i < 100; i++){ if(isCancelled()){//通過isCancelled()判斷任務任務是否被取消 break; } publishProgress(i); Thread.sleep(50);//為了看清效果,睡眠一段時間 } BufferedInputStream bis = new BufferedInputStream(is); bitmap = BitmapFactory.decodeStream(bis); is.close(); bis.close(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return bitmap; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if(isCancelled()){//通過isCancelled()判斷任務任務是否被取消 return; } mPreogressBar.setProgress(values[0]); }在doInBackground()的for循環更新進度過程中,我們持續不斷的監聽任務十分被取消,一旦取消了,盡快退出doInBackground的執行,現在運行效果如下: 可以看到,現在每次點擊“加載圖片”按鈕,新的界面都會立即更新進度條,我們就業把前面的任務給取消掉了。 使用小結: (1)AsyncTask中,只有doInBackground()方法是處於子線程中運行的,其他三個回調onPreExecute()、onPostExecute()、onProgressUpdate()都是在UI線程中進行,因此在這三個方法裡面可以進行UI的更新工作; (2)每一個new出的AsyncTask只能執行一次execute()方法,多次運行將會報錯,如需多次,需要新new一個AsyncTask; (3)AsyncTask必須在UI線程中創建實例,execute()方法也必須在UI線程中調用;
private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue可以看到,優線程池有關的都定義為了static類型,其中,SERIAL_EXECUTOR內部就定義了一個任務隊列ArrayDequesPoolWorkQueue = new LinkedBlockingQueue (128); /** * 真正用來執行任務的的線程池 */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); /** * 定義一個線程池,在線程池中有一個Runnable任務隊列,用來存放、順序執行任務 */ public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); //AsyncTask內部默認使用的線程池 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; //自定義的一個Handler private static InternalHandler sHandler;
@MainThread public final AsyncTaskAsyncTask的execute()交給了executeOnExecutor()方法,將將默認的線程池作為參數傳進來,進入executeOnExecutor方法中:execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
@MainThread public final AsyncTask上面第一步,先判斷當前AsyncTask是否正在運行或已經執行完畢,如果正在執行或執行完畢再次執行將拋出異常,這也正是我們前面在使用的時候談到,同一個AsyncTask不能多次進行execute()的原因!到了第三步的時候,先去調用一下onPreExecute()方法,因為executeOnExecutor方法本身就是在UI線程中運行的,所以onPreExecute也會在UI線程中運行。第四步,才會開始講當前AsyncTask任務加入到隊列中,我們進入默認的線程池中去看一下:executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) {//1、這裡判斷當前AsyncTask是否正在執行或已執行完畢 switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING;//2、設置正在執行的狀態 onPreExecute();//3、回調onPreExecute()方法 mWorker.mParams = params; exec.execute(mFuture);//4、放到前面默認構造的線程池中去執行 return this; }
private static class SerialExecutor implements Executor { final ArrayDeque在第四步執行execute時,實際就是調用的SerialExecutor中的execute方法,在這裡面,先創建了一個Runnable對象,然後將這個Runnable對象添加到任務隊列mTasks中,在當執行到這個Runnable時調用scheduleNext去隊列中取出一個任務,然後交給另一個線程池去真正執行這個任務。mTasks = new ArrayDeque (); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult result = (AsyncTaskResult) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } }每當收到子線程發來的和UI線程進行通信的handler請求時,先從Message中拿到子線程發來的結果參數AsyncTaskResult,AsyncTaskResult裡面封裝了AsyncTask對象和數據信息,如下:
private static class AsyncTaskResult { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } }然後根據不同狀態調用不同方法,如果是MESSAGE_POST_RESULT狀態,就調用AsyncTask的finish()方法,finish方法中會去判斷當前任務十分被cancel,如果沒有cancel則開始回調onPostExecute()方法;如果狀態是MESSAGE_POST_PROGRESS,則回調onProgressUpdate()方法。 當子線程需要和UI線程進行通信時,就會通過這個handler,往UI線程發送消息。需要通過handler來發送消息,肯定是在子線程異步任務的時候才需要,在AsyncTask中需要handler的地方其實就是兩個地方,一個是doInBackground()在運行過程中,需要更新進度值的時候;一個是doInBackground()運行完成後,需要回到到UI線程中的onPostExecute()方法的時候。 對於一:我們在doInBackground()中調用publicProgress()進行進度值的更新,因此在publicProgress()中肯定會有handler的身影,如下:
protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult對於二:其實就是一個異步任務執行完後後的返回處理,而FutureTask正是處理處理Runnable運行返回結果的。 在2.1部分的executeOnExecutor方法中第四步,我們在執行execute(mFuture),傳入了一個mFuture,mFuture是在初始化AsyncTask的時候進行構建的,如下:
mFuture = new FutureTask在上面的postResultIfNotInvoked()中會通過handler進行消息的發送。 AsyncTask原理總結: AsyncTask主要是對異步任務和handler的封裝,在處理異步任務時,AsyncTask內部使用了兩個線程池,一個線程池sDefaultExecutor是用來處理用戶提交(執行AsyncTask的execute時)過來的異步任務,這個線程池中有一個Runnable異步任務隊列ArrayDeque(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get());//這裡面通過handler往UI線程發送消息 } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } };
Android應用很多時候都會涉及到網絡,在請求網絡出錯時,我們可以通過抓包來分析網絡請求,返回的數據等,通常我們是用tcpdump這個工具來抓包,再通過wireshar
在Android的源代碼中,經常會看到形如:sp<xxx>、wp<xxx>這樣的類型定義,這其實是Android中的智能 指針。智能
程序運行效果圖: 程序代碼: /** * 獲取所有軟件信息 * 1.通過異步的方式顯示系統中所有軟件 * 2.單擊打開指定軟件 * 3.將所有軟件的包名
在Android程序中很多客戶端軟件和浏覽器軟件都喜歡用Tab分頁標簽來搭建界面框架。讀者也許會馬上想到使用TabHost 與 TabActivity的組合,其實最常用的