כשהאפליקציה נוצרת, נוצר עבורה process של מערכת ההפעלה (linux) והיא רצה על ה-Thread הראשי שלו. רק מ-Thread זה יהיה ניתן לגשת ל-UI מכאן ואילך. נקרא לו ה-UI Thread. (*)
ברור שאם האפליקציה תצטרך לבצע פעילויות ארוכות ב-Thread יחיד, ה-UI יסבול מזמני תגובה ארוכים למורת רוחו של המשתמש. נציין גם, שמערכת האנדרואיד בודקת בעצמה את תגובת האפליקציות, ואפליקציה שלא תגיב במשך חמש שניות תחשב ל-ANR - Application Not Responding, תתקבל הודעה מתאימה על המסך, והאפליקציה אף תחוסל.
ברור שאם האפליקציה תצטרך לבצע פעילויות ארוכות ב-Thread יחיד, ה-UI יסבול מזמני תגובה ארוכים למורת רוחו של המשתמש. נציין גם, שמערכת האנדרואיד בודקת בעצמה את תגובת האפליקציות, ואפליקציה שלא תגיב במשך חמש שניות תחשב ל-ANR - Application Not Responding, תתקבל הודעה מתאימה על המסך, והאפליקציה אף תחוסל.
מוסכם אם כך, שיש צורך לעיתים להשתמש ב-Threads נוספים כדי להוריד עומס מה-Thread הראשי.
נראה 2 שיטות למימוש Threads:
- שימוש ב-Handler.
- שימוש ב-AsyncTask.
(*)הערה: מקרה חריג הוא עידכון ה-ProgressBar ווידג'ט אותו הכרנו בפוסט של ה-Dialogs. הפניה אליו בטוחה מכל Thread.
ה-Handler.
ה-Handler הוא אובייקט שרץ במסגרת ה-UI Thread, והוא יושב על ה-MessageQueue ויכול לקבל ולשלוח הודעות. ההודעות יכולות לכלול data או runnables שהם מודולים להרצה. שליחת הודעות ל-MessageQueue יכולה להעשות גם מה-Thread הראשי, אך גם מ- Threads חדשים שניצור.
ההודעות וה-runnables שנשלחים ל-Message Queue יכולים לקבל טיפול מידי או אם השהיה - בהתאם לפרמטר שניתם להם. למנגנון הנ"ל יכולים להיות מספר שימושעם כגון:
ההודעות וה-runnables שנשלחים ל-Message Queue יכולים לקבל טיפול מידי או אם השהיה - בהתאם לפרמטר שניתם להם. למנגנון הנ"ל יכולים להיות מספר שימושעם כגון:
- תזמון ביצוע פעולות תך ניצול מנגנון ההשהיה.
- העברת נתונים וה-runnables בין Threads משניים לבין ה-Thread הראשי.
טיפול ב- ב-Data Messages: ה-Thread מדבר עם ה-Thread הראשי ע"י שליחת הודעות הכוללות נתונים המועברים ל--Handler - שרץ ב-Thread הראשי.
שיטת ה-Runnable: ה-Thread שולח ל-MessageQueue מודולים להרצה שיופעלו ע"י ה-Handler במסגרת ה-Thread הראשי.
נפרט את השיטות הנ"ל בשלושה פוסטים נפרדים. בפוסט הזה נדגים Handler עם Data Messages.
בפוסטים העוקבים נדגים את שתי השיטות האחרות.
Handler: טיפול ב-Data Messages
תאור הדוגמא
נציג אתה תמונת ה-UI:
בתמונה הנ"ל אפשר להבחין בארבעה חלונות TextEdit ושני כפתורים. נפרט על פעולת כל אחד מהווידג'טים הנ"ל:
כאן למטה מוצג הקוד בשלמותו. כל התוכנית בנויה בתוך class יחיד מסוג Activity. התוכנית מחולקת למספר מרכיבים, כל חלק מסומן בצבע רקע שונה:
נתחיל עם ה-Handler
הנה שוב הקוד של ה-handler:
Handler: טיפול ב-Data Messages
תאור הדוגמא
- ניצור אובייקט מסוג Handler לטיפול בהודעות שנשלחות מה-Thread. האובייקט הנ"ל רץ במסגרת ה-UI Thread, כך שהוא (ה-Handler) יכול לגשת ל-UI.
- ניצור אובייקט מסוג Thread שישלח הודעות ל-Handler הנ"ל.
- בעצם...כדי להוסיף עניין... נדגים יצירת שני אובייקטים של ה-Thread. שניהם רצים על קוד זהה, אך עם priority שונה. נשווה את ההתנהגות שלהם.
נציג אתה תמונת ה-UI:
בתמונה הנ"ל אפשר להבחין בארבעה חלונות TextEdit ושני כפתורים. נפרט על פעולת כל אחד מהווידג'טים הנ"ל:
- חלון הטקסט העליון - פשוט מאפשר הקלדת טקסט פנימה, כדי לתת למשתמש את התחושה שהמכשיר מגיב לפעולותחו תוך כדי פעולת ה-Threads ברקע.
- שני חלונות הטקסט בשורה השניה - בחלון השמאלי והימני שבשורה השניה, מודפסים הערכים הרגעיים של ה-counters שנשלחים ע"י ה-Thread הראשון והשני בהתאמה. נוכל לראות שה-counter של ה-Thread עם ה-Priority הגבוהה יותר יתקדם מהר יותר, בהתאם ליחס בין ה-Priorities. כשנעצור את ה-Thread, ה-counter שלו יעצור.
- חלון הטקסט בשורה השלישית - מציג את ה-Priorities של כל אחד מה-Threads, כאשר זו של ה-Thread הראשון ניתנת לשינוי ע"י כפתור, וה-Priority של ה-Thread השני 10תמיד . תחום ה-Priorities של ה-Threads הוא בין 1 (נמוך) ל 10.
- הכפתור השמאלי - משנה את ה-Priority של ה-Thread1. כל לחיצה מגדילה ב-1, בתחום של 1-10.
- עם שינוי ה-Priority, נאפס את ה-counters המוצגים בחלונות השורה השלישית.
- הכפתור הימני - מפעיל\מפסיק אתי -Thread1. ה-Thread השני ימשיך לרוץ, כפי שיומחש ע"י תצוגת ה-counter הרץ.
כאן למטה מוצג הקוד בשלמותו. כל התוכנית בנויה בתוך class יחיד מסוג Activity. התוכנית מחולקת למספר מרכיבים, כל חלק מסומן בצבע רקע שונה:
- Handler - צבע אפור.
- onCreate - צבע צהוב.
- ה-Thread Class - אני חושב שזהבצבע טורקיז, אבל אני לא רואה את כל הצבעים כ"כ טוב.
- איתחולי הווידג'טים וה-listeners callbacks של הכפתורים.- גוון כתום.
- threadSetup- ורוד + כתום.
public class DrawControlActivity extends Activity {
EditText editTextThreadOutput;
EditText editTextThreadOutput2;
EditText editTextPriority;
Button buttonThreadOnOff;
int prioIndx = 1;
ExampleThread exampleThread1;
ExampleThread exampleThread2;
final int THREAD2_PRIORITY = 10;
final int THREAD1_PRIORITY = 1;
Handler handler = new Handler(){
public void handleMessage(Message msg) {
int mtThreadId = msg.getData().getInt("myThreadId",1000);
int counter = msg.getData().getInt("counter",1000);
if(0 == myThreadId){
editTextThreadOutput.setText(Integer.toString(counter));
}
else
{
editTextThreadOutput2.setText(Integer.toString(counter));
}
}
};
/** Called when the activity is first created. */
@Override public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.main);
buttonSetup();
editTextSetup();
threadSetup();
}
public class ExampleThread extends Thread {
Handler mHandler;
boolean mState = true;
int counter;
int mVal;
void mPrioritySet(int priority){
setPriority(priority);
}
ExampleThread(Handler h) {
mHandler = h;
}
public void run() {
counter = 0;
while (mState) {
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
}
counter++;
Message msg = mHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("myThreadId", myThreadId);
bundle.putInt("counter", counter);
msg.setData(bundle);
mHandler.sendMessage(msg);
}
}
public void threadStateSet(boolean state) {
mState = state;
}
public boolean threadStateGet(){
return(mState);
}
public void threadIdSet(int val) {
mVal = val;
}
public void threadCounterClear() {
counter = 0;
}
}
private void buttonSetup(){
Button buttonSetPriority = (Button)findViewById(R.id.button_set_priority);
buttonSetPriority.setText("Thread Priority Set");
buttonSetPriority.setOnClickListener(
new Button.OnClickListener() {public void onClick(View v) {
if (0 == (prioIndx++)%10){
prioIndx = 1;
}
exampleThread1.mPrioritySet(prioIndx);
editTextPriority.setText("Thrd Priority = " +prioIndx+ " Thrd 2 Prio= " + THREAD2_PRIORITY);
EditText editTextThreadOutput;
EditText editTextThreadOutput2;
EditText editTextPriority;
Button buttonThreadOnOff;
int prioIndx = 1;
ExampleThread exampleThread1;
ExampleThread exampleThread2;
final int THREAD2_PRIORITY = 10;
final int THREAD1_PRIORITY = 1;
Handler handler = new Handler(){
public void handleMessage(Message msg) {
int mtThreadId = msg.getData().getInt("myThreadId",1000);
int counter = msg.getData().getInt("counter",1000);
if(0 == myThreadId){
editTextThreadOutput.setText(Integer.toString(counter));
}
else
{
editTextThreadOutput2.setText(Integer.toString(counter));
}
}
};
/** Called when the activity is first created. */
@Override public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.main);
buttonSetup();
editTextSetup();
threadSetup();
}
public class ExampleThread extends Thread {
Handler mHandler;
boolean mState = true;
int counter;
int mVal;
void mPrioritySet(int priority){
setPriority(priority);
}
ExampleThread(Handler h) {
mHandler = h;
}
public void run() {
counter = 0;
while (mState) {
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
}
counter++;
Message msg = mHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("myThreadId", myThreadId);
bundle.putInt("counter", counter);
msg.setData(bundle);
mHandler.sendMessage(msg);
}
}
public void threadStateSet(boolean state) {
mState = state;
}
public boolean threadStateGet(){
return(mState);
}
public void threadIdSet(int val) {
mVal = val;
}
public void threadCounterClear() {
counter = 0;
}
}
private void buttonSetup(){
Button buttonSetPriority = (Button)findViewById(R.id.button_set_priority);
buttonSetPriority.setText("Thread Priority Set");
buttonSetPriority.setOnClickListener(
new Button.OnClickListener() {public void onClick(View v) {
if (0 == (prioIndx++)%10){
prioIndx = 1;
}
exampleThread1.mPrioritySet(prioIndx);
editTextPriority.setText("Thrd Priority = " +prioIndx+ " Thrd 2 Prio= " + THREAD2_PRIORITY);
exampleThread1.threadCounterClear();
exampleThread2.threadCounterClear();
} });
buttonThreadOnOff = (Button)findViewById(R.id.button_thread_onoff);
buttonThreadOnOff.setText("Thread is On");
buttonThreadOnOff.setOnClickListener(
new Button.OnClickListener() {public void onClick(View v) {
if(exampleThread1.threadStateGet()){
exampleThread1.threadStateSet(false);
buttonThreadOnOff.setText("Thread is Off");
}
else
{
exampleThread1.threadStateSet(true);
buttonThreadOnOff.setText("Thread is On");
exampleThread1 = new ExampleThread(handler);
exampleThread1.start();
}
exampleThread1.mPrioritySet(prioIndx);
} });
}
private void editTextSetup(){
EditText editText1;
editTextThreadOutput = (EditText) findViewById(R.id.edit_text2);
editTextThreadOutput2 = (EditText) findViewById(R.id.edit_text22);
editText1 = (EditText) findViewById(R.id.edit_text1);
editTextPriority = (EditText) findViewById(R.id.edit_text3);
}
private void threadSetup(){
exampleThread1 = new ExampleThread(handler);
exampleThread1.start();
exampleThread1.mPrioritySet(THREAD1_PRIORITY);
editTextPriority.setText("Thrd 1 Prio= " +THREAD1_PRIORITY+ " Thrd 2 Prio= " + THREAD2_PRIORITY);
exampleThread1.threadIdSet(0);
exampleThread2 = new ExampleThread(handler);
exampleThread2.start();
exampleThread2.mPrioritySet(THREAD2_PRIORITY);
exampleThread2.threadIdSet(111111);
}
}
נתחיל עם ה-Handler
הנה שוב הקוד של ה-handler:
- Handler handler = new Handler(){
- public void handleMessage(Message msg) {
- int myThreadId = msg.getData().getInt("myThreadId",1000);
- int counter = msg.getData().getInt("counter",1000);
- if(0 == myThreadId){
- editTextThreadOutput.setText(Integer.toString(counter));
- }
- else
- {
- editTextThreadOutput2.setText(Integer.toString(counter));
- }
- }
- };
תפקיד ה-Handler בקיצור
ה-Handler ישלוף את ה-counter מההודעה, יזהה מי משני ה-thread שלח אותה, ויציג אותו בחלון השמאלי או הימני בהתאם.
שורה 2 יוצרת אובייקט של Handler בשם handler, ובאותה פקודה גם ממשת אותו.
ה-Handler מממש מתודה אחת בלבד:
handleMessage - זוהי מתודה אותה כל Handler Subcalss חייב לממש. היא מופעלת כשמגיעה הודעה מהתוד (מה-MessageQueueu) .
מבנה ההודעה שמימשנו:
ה-Thread שולח הודעה ובה שני חלקים:
ThreadId- זהוי של ה-Thread.
counter - הערך הרגעי של המונה הרץ.
כל אחד חלקי הודעה צמוד ל-key שלו (הודעות בנויות מצמדים של key,value).
תאור המתודה
שורה 3, שולפת את ה- ThreadID עפ"י ה-key לו מצפים "ThreadId". במקרה שה-key הזה לא נמצא, יוחזר ערך ה-default שהוא 0 (הפרמטר השני בשורה 3).
שורה 4 שולפת את ה-counter.
שורות 5-11 שולחות את ההודעה לאחד משני חלונות הEditText-.
נעבור ל-onCreate
כאן מתבצעות, כרגיל, פעולות האיתחול.
@Override public void onCreate(Bundle state) {
- super.onCreate(state);
- setContentView(R.layout.main);
- buttonSetup();
- editTextSetup();
- threadSetup();
- }
שורות 1-2 סטנדרטיות - קריאה ל-super constructor ופריסת ה-layout לתצוגה.
שורה 4 - buttonsSetup.
איתחול שני הכפתורים ויצירת ה-callbacks שלהם. זה כאמור לא נושא הליבה של הפוסט הזה. מופעלות שם מתודות שמקנפגות את הפרמטרים של ה-thread, כגון Priority, הפעלה והפסקה, וניקוי ערך ה-counter.
שורה 6: threadSetup.
הנה המתודה:
private void threadSetup(){
- exampleThread1 = new ExampleThread(handler);
- exampleThread1.start();
- exampleThread1.mPrioritySet(THREAD1_PRIORITY);
- editTextPriority.setText("Thrd 1 Prio= " +THREAD1_PRIORITY+ " Thrd 2 Prio= " + THREAD2_PRIORITY);
- exampleThread1.threadIdSet(0);
- exampleThread2 = new ExampleThread(handler);
- exampleThread2.start();
- exampleThread2.mPrioritySet(THREAD2_PRIORITY);
- exampleThread2.threadIdSet(111111);
- }
- }
שורה 2\שורה 7: המתודה יוצרת שני אובייקטים של ה- ExampleThread
שורה 4\9: מקנפגת את ה-priority
שורה 5: מציגה את ה-priority ההתחלתי בחלון התצוגה.
שורה 6\10: מקנפגת את הזיהוי, שהיא בעצם חלק מההודעה אותה ישלחו ה-Threads ובעזרתה נבחין בינהם
שורה 3\8: מפעילה את ה-Threads ע"י המתודה start.
ה-Thread Class
ה-constructor - שורות 10-12, שומר את האובייקט של ה-handler. אובייקט זה ישמש לצורך שליחת ההודעות.
המתודה העיקרית ב-class ה-Thread היא -run. מתודה זו מופעלת כתוצאה מהפעלת start. ה-run כולל לולאת while - שורות 15-28. בתוך הלולאה ישנה פקודת ה-(sleep(100 , בה הThread ישן למשך 100 מילישניה.
שורות 22-27 הן השורות בהן ה-Thread יוצר ושולח את ההודעה ל-handler.
public class ExampleThread extends Thread {
- Handler mHandler;
- boolean mState = true;
- int counter;
- int mVal;
- void mPrioritySet(int priority){
- setPriority(priority);
- }
- ExampleThread(Handler h) {
- mHandler = h;
- }
- public void run() {
- counter = 0;
- while (mState) {
- try {
- Thread.sleep(10);
- }
- catch (InterruptedException e) {
- }
- counter++;
- Message msg = mHandler.obtainMessage();
- Bundle bundle = new Bundle();
- bundle.putInt("myThreadId", myThreadId);
- bundle.putInt("counter", counter);
- msg.setData(bundle);
- mHandler.sendMessage(msg);
- }
- }
- public void threadStateSet(boolean state) {
- mState = state;
- }
- public boolean threadStateGet(){
- return(mState);
- }
- public void threadIdSet(int val) {
- mVal = val;
- }
- public void threadCounterClear() {
- counter = 0;
- }
- }
נתרכז ביצירת ההודעה:
- Message msg = mHandler.obtainMessage();
- Bundle bundle = new Bundle();
- bundle.putInt("myThreadId", myThreadId);
- bundle.putInt("counter", counter);
- msg.setData(bundle);
- mHandler.sendMessage(msg);
שורה 1: יצירת אובייקט מסוג Message. זה האובייקט שיישלח ל MessageQueueu ומשם יגיע ל-Handler.
שורה 2-4: הכנת ההודעה. לצורך הכנת ההודעה משתמשים באובייקט מסוג Bundle שמשמש לבנייתה. כבר ציינו שההודעה בנויה מזוגות של key,value. שני זוגות כאלה מוכנסים בשורות 3 ו-4.
שורה 4 מכניסה את ה-bundle לתוך ההודעה.
שורה 6: שליחת ההודעה ל-message queue השייך ל- mHandler.
מימשנו Thread עם הודעות שטופלו ע"י ה-Handler. בפוסטים הבאים נסקור את מימוש ה-Thread עם runnable ועם AsyncTask.
אחלה הסבר!
השבמחקרק חבל שבקוד המתודות לא כתובות בתבנית הרווחת - threadIdSet -> setThreadId...
אבל באמת כל הכבוד ותודה!
תודה רבה!
השבמחקעוזר לי מאד!