【代碼】Android中的進程與多線程的講解(Handler和AsyncTask)

 Hello EveryBody,又到了我們相聚的時間了,今天要總結的東西現在有點迫不及待了,因爲在實際的應用中如果用不到它,我們就不能再聽歌的同時發送信息,其實大家應該都知道了,今天的主角就是進程與多線程,好了,其他的不多說,直接進入正題吧。


一.Android進程的分類:
(1)前臺進程:即與用戶正在交互的Activity或者Activity用到的Service等,如果系統內存不足時前臺進程是最後被殺死的;
(2)可見進程:可以是處於暫停狀態(onPause)的Activity或者綁定在其上的Service,即被用戶可見,但由於失去了焦點而不能與用戶交互;
(3)服務進程:其中運行着使用startService方法啓動的Service,雖然不被用戶可見,但是卻是用戶關係的,例如用戶正在非音樂界面聽的音樂或者正在非下載頁面自己下載的文件等;當系統要用空間運行前兩者進程時纔會被終止;
(4)後臺進程:其中運行着執行onStop方法而停止的程序,但是卻不是用戶當前關心的,例如後臺掛着的QQ,這樣的進程系統一旦沒有內存就首先被殺死;
(5)空進程:不包含任何應用程序的程序組件的進程,這樣的進程系統是一般不會讓他存在的;


二.進程與多線程的深入
1.由於單線程而導致的ANR錯誤:
   ANR錯誤(Application Not Responding),指主UI進程被阻塞超過5秒鐘而出現的錯誤,它會終止程序的正常運行,我們要避免它 ,而產生ANR錯誤的原因就是:單線程。
例子如下:

package com.example.l0902_anr;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=(Button) findViewById(R.id.btn);
        btn.setOnClickListener(new OnClickListener() {
                                                                                                                                                                                                                                                                                                                                                                                                                                                     
            @Override
            public void onClick(View v) {
                int count=0;
                while(count<1000){
                    count++;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    }
}


運行效果如下:
http://img1.51cto.com/attachment/201309/185351138.jpg



OK,上面的程序出問題的原因是單線程,那麼我們可以再創建一個線程去處理耗時操作,就不會阻礙主UI的操作了:
改動上面的程序如下:

package com.example.l0902_anr;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
private Button btn,btn_stop;
private Thread thread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=(Button) findViewById(R.id.btn);
        btn_stop=(Button) findViewById(R.id.button1);
        btn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                thread=new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int count=0;
                        while(count<1000){
                            count++;
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(count);
                        }
                    }
                });
                thread.start();//啓動線程
            }
        });
        btn_stop.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                thread.stop();//停止線程
            }
        });
    }
}


運行效果如下:
http://img1.51cto.com/attachment/201309/191830584.jpg

代碼寫到這裏,ANR錯誤就解決了,但是又出現一個新的問題,如果我想把利用新的線程來更新主UI線程,可以不可以呢,好吧,我們用上面的程序試一試,加一個TextView來接收新的線程中自增的數據,看看是否能實現:
程序修改如下:

package com.example.l0902_anr;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button btn;
private TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=(Button) findViewById(R.id.btn);
        tv=(TextView) findViewById(R.id.tv);
        btn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int count=0;
                        while(count<1000){
                            count++;
                            tv.setText(count+"");//試圖更新主UI線程
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();//啓動線程
            }
        });
    }
}

運行效果如下:
http://img1.51cto.com/attachment/201309/192348528.jpg

我們可以看到程序被意外終止了,那麼說明其他線程是不能更改主UI的數據的,除非...除非我們往下看...


2.Android線程之間的通信——Handler
   其實,Android線程之間的通信不只是Handler,還需要Message,MessageQueue,Looper的相互使用,Android線程通信模型如下:
http://img1.51cto.com/attachment/201309/192914506.png

(1)Message:即要傳遞的消息;
(2)MessageQueue:存放消息的隊列;
(3)Looper:用於創建MessageQueue以及循環使用其中的Message;
(4)Handler:用於消息的傳遞了;
好了,我們直接看下面的例子吧:

package com.example.l0902_handler;
import android.annotation.SuppressLint;
import android.app.Activity;
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 MainActivity extends Activity {
    private Button btn_start,btn_stop;
    private TextView tv;
    private int i=0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_start=(Button) findViewById(R.id.button1);
        btn_stop=(Button) findViewById(R.id.button2);
        tv=(TextView) findViewById(R.id.textView1);
        btn_start.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //開始發送消息
                handler.post(runnabl);
            }
        });
        btn_stop.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //停止發送消息
                handler.removeCallbacks(runnabl);
            }
        });
    }
    //創建新的線程
    private Runnable runnabl=new Runnable() {
        @Override
        public void run() {
            i++;
            //使用Message封裝非UI線程的消息
            Message m=new Message();
            //使用Message的arg1屬性或者arg2屬性傳遞int類型的消息效率高
            m.arg1=i;
            //使用Handler發送消息
            handler.sendMessage(m);
        }
    };
    @SuppressLint("HandlerLeak")
    private Handler handler=new Handler(){
        public void handleMessage(Message m){
            System.out.println(m.arg1);
            String str=m.arg1+"";
            //注意:一定一定要記得TextView是String類型哦
            tv.setText(str);
            handler.post(runnabl);
        }
    };
}


運行結果如下:

http://img1.51cto.com/attachment/201309/194146734.jpg

http://img1.51cto.com/attachment/201309/194146231.jpg


看到了吧,主UI的數據是不是更新了呢,很是神奇吧,這就是線程之間的通信了。
接下來我們要繼續講一個線程通信的機制。。。


3.簡化多線程通信開發的——AsyncTask

 我們直接用例子說明AsyncTask的使用吧:

(1)我們同樣也先寫一個類似上面的ANR錯誤的卡屏的例子:


package com.example.l0902_myasynctask;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
    private Button btn1,btn2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1=(Button) findViewById(R.id.button1);
        btn2=(Button) findViewById(R.id.button2);
        btn1.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    //超過5秒了,其實就和前面的例子一樣了(ANR錯誤)
                    Thread.sleep(7000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        btn2.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("Hello To EveryOne");
            }
        });
    }
}

運行效果如下:
http://img1.51cto.com/attachment/201309/195758374.jpg

如果把上面等待的時間改爲5以內,則就是典型的卡屏的例子了,試試看吧:
初始界面:
http://img1.51cto.com/attachment/201309/200316867.jpg


點擊下面的按鈕:
http://img1.51cto.com/attachment/201309/200228728.jpg


點擊上面的按鈕:此時就卡住了,屏幕什麼都幹不了了

http://img1.51cto.com/attachment/201309/200228148.jpg


卡屏期間點擊了多少下下邊的按鈕,5秒之後都會顯示出來:
http://img1.51cto.com/attachment/201309/200228720.jpg

上面就是經典的卡屏的例子,怎麼解決呢?當然是:AsyncTask了

(2)使用AsyncTask創建後臺進程,解決卡屏問題:

代碼如下:
MainActivity.java

package com.example.l0902_myasynctask;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
    private Button btn1,btn2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1=(Button) findViewById(R.id.button1);
        btn2=(Button) findViewById(R.id.button2);
        btn1.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //創建AsyncTask並執行
                new MyAsyncTask().execute();
            }
        });
        btn2.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("Hello To EveryOne");
            }
        });
    }
}


MyAsyncTask.java:

package com.example.l0902_myasynctask;
import android.os.AsyncTask;
/**
 * 在後臺創建線程,不會出現卡屏現象
 * @author asus
 */
public class MyAsyncTask extends AsyncTask<Void, Void, String>{
    //核心方法,在後臺啓動一個線程
    @Override
    protected String doInVoid...  params) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}


運行效果我們就不再給出了,描述一下就可以了,即無論我們是否按卡屏按鈕,執行按鈕都能正常輸出,這正是因爲AsyncTask創建了一個後臺進程來處理耗時功能,不影響主UI的正常功能的處理。


(3)最後總結一個使用AsyncTask的例子——下載滾動條的進度:
MainActivity.java:

package com.example.l0902_usinganysc;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
    private Button btn;
    private ProgressBar bar;
    private TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=(Button) findViewById(R.id.button1);
        bar=(ProgressBar) findViewById(R.id.progressBar1);
        tv=(TextView) findViewById(R.id.textView1);
        tv.setText("開始下載");
        btn.setOnClickListener(new OnClickListener() {
                                                        
            @Override
            public void onClick(View v) {
                MyAsyncTask my=new MyAsyncTask(tv, bar);
                my.execute("","i=","打開文件");//啓動方法
            }
        });
    }
}


MyAsyncTask.java:

package com.example.l0902_usinganysc;
import android.os.AsyncTask;
import android.widget.ProgressBar;
import android.widget.TextView;
/**
 * 總結:參數相關
 * @author asus
 */
public class MyAsyncTask extends AsyncTask<String, Integer, String>{
    private ProgressBar bar;
    private TextView tv;
    //構造方法,初始化ProgressBar和TextView
    public MyAsyncTask(TextView tv,ProgressBar bar){
        this.bar=bar;
        this.tv=tv;
    }
    /*
     * 這個方法的參數不能改,其返回類型與第三個參數一致,其參數與第一個參數類型一致
     * (non-Javadoc)
     * @see android.os.AsyncTask#doInParams[])
      */
    @Override
    protected String doInString...  params) {//可變參數
        String p1=params[1];
        String p2=params[2];
        int i=1;
        for(i=1;i<=100;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            publishProgress(i);
        }
        return p1+i+p2;//也作爲下一個方法的參數
    }
    /*
     * 當異步結束時觸發此方法,其參數類型與第三個參數類型一致
     * (non-Javadoc)
     * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
     */
    @Override
    protected void onPostExecute(String result) {
        if(result!=null){
            tv.setText("下載完成"/*+result*/);
        }
        super.onPostExecute(result);
    }
    /*
     * 當異步開始的時候觸發
     * (non-Javadoc)
     * @see android.os.AsyncTask#onPreExecute()
     */
    @Override
    protected void onPreExecute() {
        System.out.println("異步開始");
        tv.setText("開始下載");
        super.onPreExecute();
    }
    /*
     * 正在處理的時候觸發,與主UI線程交互,其參數與第二個參數一致
     * (non-Javadoc)
     * @see android.os.AsyncTask#onProgressUpdate(Progress[])
     */
    @Override
    protected void onProgressUpdate(Integer... values) {//第二個可變參數,由上面的publishProgress方法的參數決定
        bar.setProgress(values[0]);
        tv.setText(values[0]+"%");//可變參數就是這麼用的,values[1]表示publishProgress的第二個參數
        super.onProgressUpdate(values);
    }
}


運行效果如下:

http://img1.51cto.com/attachment/201309/202739378.jpg

http://img1.51cto.com/attachment/201309/202739206.jpg

http://img1.51cto.com/attachment/201309/202739491.jpg


好了。今天的內容就到這裏了,希望真的能幫到你,如果有需要改進的地方請多多指出批評和指點,我欣然接受哦O(∩_∩)O哈哈~
發佈了31 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章