1. 程式人生 > >Android開發之多執行緒的操作方式Thread,TimerTask,AsyncTask

Android開發之多執行緒的操作方式Thread,TimerTask,AsyncTask

雨鬆MOMO原創文章如轉載,請註明:轉載至我的獨立域名部落格雨鬆MOMO程式研究院,原文地址:http://www.xuanyusong.com/archives/344

Android研究院之遊戲開發多執行緒(十六)

 

Android研究院之遊戲開發多執行緒(十六) - 雨鬆MOMO程式研究院 - 1
遊戲開發與軟體開發多執行緒的重要性

      如果程式主執行緒被阻塞超過5秒,系統會提示“應用程式無響應” 這就是ANR 。 ANR的全稱是Application Not Responding,使用多執行緒可以避免ANR。但是這裡要注意一下不要為了避免ANR而過多的使用多執行緒,除非萬不得已的情況。 比如訪問網路服務端返回的過慢、資料過多導致滑動螢幕不流暢、或者I/O讀取過大的資源等等。這裡可以開啟一個新執行緒來處理這些耗時的操作。 如果過多使用多執行緒會出現資料同步的問題須要程式設計師去處理,所以使用多執行緒的時候儘量保持它的獨立不會被其它執行緒干預。java語言提供了一個執行緒鎖的概念 synchronized 可以新增物件鎖與方法鎖專門避免多執行緒同時訪問一個方法或者一個物件導致的問題,有興趣的朋友可以去看看這裡我不羅嗦啦 。

1.Thread與Handler執行多執行緒

Android研究院之遊戲開發多執行緒(十六) - 雨鬆MOMO程式研究院 - 2

Handler主要用於程式主執行緒與我們自己建立的執行緒進行通訊。在這個例子中點選按鈕後建立一個新的執行緒去迴圈的載入100張圖片每載入完一張圖片在Thread中使用Handler傳送訊息通知UI執行緒更新顯示,直到加在完畢通知UI顯示載入完成一共耗時多少秒。可見Handler的重要性它就是主執行緒與我們自己建立的執行緒的橋樑啊~~~

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

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++) {

    // 這裡迴圈載入圖片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延遲多執行緒

Android研究院之遊戲開發多執行緒(十六) - 雨鬆MOMO程式研究院 - 3

Timer與TimerTask可以構建一個延遲器 就好比開啟一個執行緒每隔一段規定的時間訪問一次。可以在這個執行緒中去關閉這個Timer 與TimerTask ,舉個例子比如現在我要做一個網遊帳號登入超時客戶端的檢測 使用者輸入完帳號密碼點選登入這時候我開啟一個TimerTask每過1秒檢查一下使用者是否登入成功,過了10秒如果還沒有登入成功提示他登陸超時。這個時候我就須要在這個檢測執行緒中去關閉Timer 與TimerTask  因為不需要它在迴圈檢測了。 呼叫cancel()就可以關閉,請同學們閱讀下面這個例子。

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

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 {

 

    /**執行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已經關閉請重新開啟" );

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) {

//開始執行timer

StartTimer();

    }

});

 

//關閉

mButton1.setOnClickListener(new OnClickListener() {

    @Override

    public void onClick(View arg0) {

//停止執行timer

CloseTimer();

    }

});

 

super.onCreate(savedInstanceState);

    }

 

    public void StartTimer() {

 

if (mTimer == null) {

    mTimerTask = new TimerTask() {

public void run() {

    //mTimerTask與mTimer執行的前提下每過1秒進一次這裡

    mTimerID ++;

    Message msg = new Message();

    msg.what = LOAD_PROGRESS;

    msg.arg1 = (int) (mTimerID);

    handler.sendMessage(msg);

}

    };

    mTimer = new Timer();

 

    //第一個引數為執行的mTimerTask

    //第二個引數為延遲的時間 這裡寫1000的意思是mTimerTask將延遲1秒執行

    //第三個引數為多久執行一次 這裡寫1000表示每1秒執行一次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;

 

//這裡傳送一條只帶what空的訊息

handler.sendEmptyMessage(CLOSE_PROGRESS);

    }

}

3.AsyncTask執行多執行緒

Android研究院之遊戲開發多執行緒(十六) - 雨鬆MOMO程式研究院 - 4

執行AsyncTask

onPreExecute()///首先執行這個方法,它在UI執行緒中 可以執行一些非同步操作 比如初始化一些東西
doInBackground(Object… arg0) //非同步後臺執行 ,執行完畢可以返回出去一個結果object物件
onPostExecute(Object result) //可以拿到執行中的進度 當然進度須要在doInBackground中手動呼叫publishProgress()方法返回
通過例子可以清楚的看到計算出讀取100張圖片的時間,執行的效率上來說AsyncTask 沒有Thread效率塊,但是AsyncTask 比Thread更規整,它可是時時的拿到非同步執行緒中進度以及最後的結果集,可以讓我們的程式碼更佳規範。這裡說一下 Thread能做到的事AsyncTask 都可以做到 但是AsyncTask  能做到的事Thread 不一定能做到就算勉強做到也很麻煩 。我給大家的建議是如果處理大量的非同步操作就用AsyncTask 如果少部分的則使用Thread

 

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

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 {

 

    /**執行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) {

//開始執行StartAsync

StartAsync();

    }

});

 

super.onCreate(savedInstanceState);

    }

 

    public void StartAsync() {

new AsyncTask<Object, Object, Object>() {

 

    @Override

    protected void onPreExecute() {

//首先執行這個方法,它在UI執行緒中 可以執行一些非同步操作

mTextView.setText("開始載入進度");

super.onPreExecute();

    }

 

    @Override

    protected Object doInBackground(Object... arg0) {

//非同步後臺執行 ,執行完畢可以返回出去一個結果object物件

 

//得到開始載入的時間

Long startTime = System.currentTimeMillis();

for (int i = 0; i < 100; i++) {

    // 這裡迴圈載入圖片100遍

    ReadBitMap(mContext, R.drawable.bg);

    //執行這個方法會非同步呼叫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();//可以理解為執行 這個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的使用

 

Android研究院之遊戲開發多執行緒(十六) - 雨鬆MOMO程式研究院 - 5

Looper用來管理執行緒的訊息佇列與迴圈佇列,在handler中預設為mainlooper來進行訊息迴圈,如果在handler中開啟一個新的執行緒那麼在這個新的執行緒中就沒有Looper迴圈,如果想讓這個新的執行緒具有訊息佇列與訊息迴圈我們須要呼叫 Looper.prepare();拿到它的loop ,這樣就好比在Thread中建立了訊息佇列與迴圈  需要呼叫   Looper.loop(); 它的意思就是執行這個訊息迴圈,下面我給出一個例子希望大家好好閱讀。

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

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 {

    /** 傳送訊息按鈕 **/

    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來進行訊息迴圈,

    //而當前是在一個新的執行緒中它沒有預設的looper

    //所以我們須要手動呼叫prepare()拿到他的loop

    //可以理解為在Thread建立Looper的訊息佇列

    Looper.prepare();

 

    Toast.makeText(LooperActivity.this, "收到訊息",Toast.LENGTH_LONG).show();

 

    //在這裡執行這個訊息迴圈如果沒有這句

    //就好比只建立了Looper的訊息佇列而

    //沒有執行這個佇列那麼上面Toast的內容是不會顯示出來的

    Looper.loop();

 

//如果沒有   Looper.prepare();  與 Looper.loop();

//會丟擲異常Can't 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() {

 

    //傳送一條空的訊息

            //空訊息中必需帶一個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);

    }

}

老規矩每篇文章都會附帶原始碼,最後如果你還是覺得我寫的不夠詳細 看的不夠爽 不要緊我把原始碼的下載地址貼出來 歡迎大家一起討論學習雨鬆MOMO希望可以和大家一起進步。

 

下載地址:http://vdisk.weibo.com/s/aakLw