編輯:關於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()中
}
}
布局文件如下:
效果如下: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" />
由於需要聯網,注意在AndroidManifest.xml中加入網絡訪問權限。
因此,在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 sPoolWorkQueue =
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;
可以看到,優線程池有關的都定義為了static類型,其中,SERIAL_EXECUTOR內部就定義了一個任務隊列ArrayDeque@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 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);
}
}
}
在第四步執行execute時,實際就是調用的SerialExecutor中的execute方法,在這裡面,先創建了一個Runnable對象,然後將這個Runnable對象添加到任務隊列mTasks中,在當執行到這個Runnable時調用scheduleNext去隊列中取出一個任務,然後交給另一個線程池去真正執行這個任務。
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抓包應用實現
Android應用很多時候都會涉及到網絡,在請求網絡出錯時,我們可以通過抓包來分析網絡請求,返回的數據等,通常我們是用tcpdump這個工具來抓包,再通過wireshar
android refbase類
在Android的源代碼中,經常會看到形如:sp<xxx>、wp<xxx>這樣的類型定義,這其實是Android中的智能 指針。智能
Android開發之獲取所有軟件信息
程序運行效果圖: 程序代碼: /** * 獲取所有軟件信息 * 1.通過異步的方式顯示系統中所有軟件 * 2.單擊打開指定軟件 * 3.將所有軟件的包名
Android入門之ActivityGroup+GridView實現Tab分頁標簽的方法
在Android程序中很多客戶端軟件和浏覽器軟件都喜歡用Tab分頁標簽來搭建界面框架。讀者也許會馬上想到使用TabHost 與 TabActivity的組合,其實最常用的