|
Android游戲開發(fā)之多線程的操作方式
游戲開發(fā)與軟件開發(fā)多線程的重要性
如果程序主線程被阻塞超過5秒,系統(tǒng)會提示“應用程序無響應” 這就是ANR 。 ANR的全稱是Application Not Responding,使用多線程可以避免ANR。但是這里要注意一下不要為了避免ANR而過多的使用多線程,除非萬不得已的情況。 比如訪問網(wǎng)絡服務端返回的過慢、數(shù)據(jù)過多導致滑動屏幕不流暢、或者I/O讀取過大的資源等等。這里可以開啟一個新線程來處理這些耗時的操作。 如果過多使用多線程會出現(xiàn)數(shù)據(jù)同步的問題須要程序員去處理,所以使用多線程的時候盡量保持它的獨立不會被其它線程干預。java語言提供了一個線程鎖的概念 synchronized 可以添加對象鎖與方法鎖專門避免多線程同時訪問一個方法或者一個對象導致的問題,有興趣的朋友可以去看看這里我不羅嗦啦 。
1.Thread與Handler執(zhí)行多線程
Handler主要用于程序主線程與我們自己創(chuàng)建的線程進行通信。在這個例子中點擊按鈕后創(chuàng)建一個新的線程去循環(huán)的加載100張圖片每加載完一張圖片在Thread中使用Handler發(fā)送消息通知UI線程更新顯示,直到加在完畢通知UI顯示加載完成一共耗時多少秒。可見Handler的重要性它就是主線程與我們自己創(chuàng)建的線程的橋梁啊~~~ - import java.io.InputStream;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class SingleActivity extends Activity {
/**讀取進度**/
public final static int LOAD_PROGRESS = 0;
/**標志讀取進度結束**/
public final static int LOAD_COMPLETE = 1;
/** 開始加載100張圖片按鈕 **/
Button mButton = null;
/** 顯示內容 **/
TextView mTextView = null;
/** 加載圖片前的時間 **/
Long mLoadStatr = 0L;
/** 加載圖片后的時間 **/
Long mLoadEnd = 0L;
Context mContext = null;
//接收傳遞過來的信息
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case LOAD_PROGRESS:
mTextView.setText("當前讀取到第" + msg.arg1 + "張圖片");
break;
case LOAD_COMPLETE:
mTextView.setText("讀取結束一共耗時" + msg.arg1 + "毫秒");
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.single);
mContext = this;
/** 拿到button 與 TextView 對象 **/
mButton = (Button) findViewById(R.id.button0);
mTextView = (TextView) findViewById(R.id.textView0);
mTextView.setText("點擊按鈕開始更新時間");
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//開始讀取圖片
LoadImage();
}
});
super.onCreate(savedInstanceState);
}
public void LoadImage() {
new Thread() {
@Override
public void run() {
//得到加載圖片開始的時間
mLoadStatr = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
// 這里循環(huán)加載圖片100遍
ReadBitMap(mContext, R.drawable.bg);
// 每讀取完一張圖片將進度甩給handler
Message msg = new Message();
msg.what = LOAD_PROGRESS;
msg.arg1 = i + 1;
handler.sendMessage(msg);
}
//得到加載圖片結束的時間
mLoadEnd = System.currentTimeMillis();
//100張圖片加載完成
Message msg = new Message();
msg.what = LOAD_COMPLETE;
msg.arg1 = (int) (mLoadEnd - mLoadStatr);
handler.sendMessage(msg);
}
}.start();
}
/**
* 讀取本地資源的圖片
*
* @param context
* @param resId
* @return
*/
public Bitmap ReadBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
// 獲取資源圖片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
}
復制代碼 2.TimerTask與Handler延遲多線程
Timer與TimerTask可以構建一個延遲器 就好比開啟一個線程每隔一段規(guī)定的時間訪問一次。可以在這個線程中去關閉這個Timer 與TimerTask ,舉個例子比如現(xiàn)在我要做一個網(wǎng)游帳號登錄超時客戶端的檢測 用戶輸入完帳號密碼點擊登錄這時候我開啟一個TimerTask每過1秒檢查一下用戶是否登錄成功,過了10秒如果還沒有登錄成功提示他登陸超時。這個時候我就須要在這個檢測線程中去關閉Timer 與TimerTask 因為不需要它在循環(huán)檢測了。 調用cancel()就可以關閉,請同學們閱讀下面這個例子。 - import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class TimerTaskActivity extends Activity {
/**執(zhí)行Timer進度**/
public final static int LOAD_PROGRESS = 0;
/**關閉Timer進度**/
public final static int CLOSE_PROGRESS = 1;
/** 開始TimerTask按鈕 **/
Button mButton0 = null;
/** 關閉TimerTask按鈕 **/
Button mButton1 = null;
/** 顯示內容 **/
TextView mTextView = null;
Context mContext = null;
/**Timer對象**/
Timer mTimer = null;
/**TimerTask對象**/
TimerTask mTimerTask = null;
/**記錄TimerID**/
int mTimerID = 0;
/**接收傳遞過來的信息**/
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case LOAD_PROGRESS:
mTextView.setText("當前TimerID為" + msg.arg1 );
break;
case CLOSE_PROGRESS:
mTextView.setText("當前Timer已經(jīng)關閉請重新開啟" );
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.timer);
mContext = this;
/** 拿到button 與 TextView 對象 **/
mButton0 = (Button) findViewById(R.id.button0);
mButton1 = (Button) findViewById(R.id.button1);
mTextView = (TextView) findViewById(R.id.textView0);
mTextView.setText("點擊按鈕開始更新時間");
//開始
mButton0.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//開始執(zhí)行timer
StartTimer();
}
});
//關閉
mButton1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//停止執(zhí)行timer
CloseTimer();
}
});
super.onCreate(savedInstanceState);
}
public void StartTimer() {
if (mTimer == null) {
mTimerTask = new TimerTask() {
public void run() {
//mTimerTask與mTimer執(zhí)行的前提下每過1秒進一次這里
mTimerID ++;
Message msg = new Message();
msg.what = LOAD_PROGRESS;
msg.arg1 = (int) (mTimerID);
handler.sendMessage(msg);
}
};
mTimer = new Timer();
//第一個參數(shù)為執(zhí)行的mTimerTask
//第二個參數(shù)為延遲的時間 這里寫1000的意思是mTimerTask將延遲1秒執(zhí)行
//第三個參數(shù)為多久執(zhí)行一次 這里寫1000表示每1秒執(zhí)行一次mTimerTask的Run方法
mTimer.schedule(mTimerTask, 1000, 1000);
}
}
public void CloseTimer() {
//在這里關閉mTimer 與 mTimerTask
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask = null;
}
/**ID重置**/
mTimerID = 0;
//這里發(fā)送一條只帶what空的消息
handler.sendEmptyMessage(CLOSE_PROGRESS);
}
}
復制代碼 3.AsyncTask執(zhí)行多線程
執(zhí)行AsyncTask
onPreExecute()///首先執(zhí)行這個方法,它在UI線程中 可以執(zhí)行一些異步操作 比如初始化一些東西
doInBackground(Object... arg0) //異步后臺執(zhí)行 ,執(zhí)行完畢可以返回出去一個結果object對象
onPostExecute(Object result) //可以拿到執(zhí)行中的進度 當然進度須要在doInBackground中手動調用publishProgress()方法返回
通過例子可以清楚的看到計算出讀取100張圖片的時間,執(zhí)行的效率上來說AsyncTask 沒有Thread效率塊,但是AsyncTask 比Thread更規(guī)整,它可是時時的拿到異步線程中進度以及最后的結果集,可以讓我們的代碼更佳規(guī)范。這里說一下 Thread能做到的事AsyncTask 都可以做到 但是AsyncTask 能做到的事Thread 不一定能做到就算勉強做到也很麻煩 。我給大家的建議是如果處理大量的異步操作就用AsyncTask 如果少部分的則使用Thread - import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class AsyncTaskActivity extends Activity {
/**執(zhí)行Timer進度**/
public final static int LOAD_PROGRESS = 0;
/**關閉Timer進度**/
public final static int CLOSE_PROGRESS = 1;
/** 開始StartAsync按鈕 **/
Button mButton0 = null;
/** 顯示內容 **/
TextView mTextView = null;
Context mContext = null;
/**Timer對象**/
Timer mTimer = null;
/**TimerTask對象**/
TimerTask mTimerTask = null;
/**記錄TimerID**/
int mTimerID = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.async);
mContext = this;
/** 拿到button 與 TextView 對象 **/
mButton0 = (Button) findViewById(R.id.button0);
mTextView = (TextView) findViewById(R.id.textView0);
//開始
mButton0.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//開始執(zhí)行StartAsync
StartAsync();
}
});
super.onCreate(savedInstanceState);
}
public void StartAsync() {
new AsyncTask<Object, Object, Object>() {
@Override
protected void onPreExecute() {
//首先執(zhí)行這個方法,它在UI線程中 可以執(zhí)行一些異步操作
mTextView.setText("開始加載進度");
super.onPreExecute();
}
@Override
protected Object doInBackground(Object... arg0) {
//異步后臺執(zhí)行 ,執(zhí)行完畢可以返回出去一個結果object對象
//得到開始加載的時間
Long startTime = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
// 這里循環(huán)加載圖片100遍
ReadBitMap(mContext, R.drawable.bg);
//執(zhí)行這個方法會異步調用onProgressUpdate方法,可以用來更新UI
publishProgress(i);
}
//得到結束加載的時間
Long endTime = System.currentTimeMillis();
//將讀取時間返回
return endTime - startTime;
}
@Override
protected void onPostExecute(Object result) {
//doInBackground之行結束以后在這里可以接收到返回的結果對象
mTextView.setText("讀取100張圖片一共耗時" + result+ "毫秒");
super.onPostExecute(result);
}
@Override
protected void onProgressUpdate(Object... values) {
//時時拿到當前的進度更新UI
mTextView.setText("當前加載進度" + values[0]);
super.onProgressUpdate(values);
}
}.execute();//可以理解為執(zhí)行 這個AsyncTask
}
/**
* 讀取本地資源的圖片
*
* @param context
* @param resId
* @return
*/
public Bitmap ReadBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
// 獲取資源圖片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
}
復制代碼 4.多線程Looper的使用
Looper用來管理線程的消息隊列與循環(huán)隊列,在handler中默認為mainlooper來進行消息循環(huán),如果在handler中開啟一個新的線程那么在這個新的線程中就沒有Looper循環(huán),如果想讓這個新的線程具有消息隊列與消息循環(huán)我們須要調用 Looper.prepare();拿到它的loop ,這樣就好比在Thread中創(chuàng)建了消息隊列與循環(huán) 需要調用 Looper.loop(); 它的意思就是執(zhí)行這個消息循環(huán),下面我給出一個例子希望大家好好閱讀。 - import java.io.InputStream;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class LooperActivity extends Activity {
/** 發(fā)送消息按鈕 **/
Button mButton = null;
/** 加載圖片前的時間 **/
Long mLoadStatr = 0L;
/** 加載圖片后的時間 **/
Long mLoadEnd = 0L;
Context mContext = null;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
new Thread() {
@Override
public void run() {
//如果handler不指定looper的話
//默認為mainlooper來進行消息循環(huán),
//而當前是在一個新的線程中它沒有默認的looper
//所以我們須要手動調用prepare()拿到他的loop
//可以理解為在Thread創(chuàng)建Looper的消息隊列
Looper.prepare();
Toast.makeText(LooperActivity.this, "收到消息",Toast.LENGTH_LONG).show();
//在這里執(zhí)行這個消息循環(huán)如果沒有這句
//就好比只創(chuàng)建了Looper的消息隊列而
//沒有執(zhí)行這個隊列那么上面Toast的內容是不會顯示出來的
Looper.loop();
//如果沒有 Looper.prepare(); 與 Looper.loop();
//會拋出異常Cant create handler inside thread that has not called Looper.prepare()
//原因是我們新起的線程中是沒有默認的looper所以須要手動調用prepare()拿到他的loop
}
}.start();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.loop);
mContext = this;
/** 拿到button 與 TextView 對象 **/
mButton = (Button) findViewById(R.id.button0);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
new Thread() {
@Override
public void run() {
//發(fā)送一條空的消息
//空消息中必需帶一個what字段
//用于在handler中接收
//這里暫時我先寫成0
handler.sendEmptyMessage(0);
}
}.start();
}
});
super.onCreate(savedInstanceState);
}
/**
* 讀取本地資源的圖片
*
* @param context
* @param resId
* @return
*/
public Bitmap ReadBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
// 獲取資源圖片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
}
復制代碼 老規(guī)矩每篇文章都會附帶源代碼,最后如果你還是覺得我寫的不夠詳細 看的不夠爽 不要緊我把源代碼的下載地址貼出來 歡迎大家一起討論學習
第十六講多線程的操作方式.rar(375.16 KB, 下載次數(shù): 453)[/I]2011-9-3 01:03 上傳點擊文件名 下載積分: 下載豆 -2 |
上一篇: android讀取excel的數(shù)據(jù)下一篇: Android 軟件開發(fā)之數(shù)據(jù)的 新建 儲存 讀取 刪除 詳解(十四)
|