Android 開發中的多線程編程技術

 Java中的線程 html

  Java的線程類是java.lang.Thread類。當生成一個Thread類的對象以後,一個新的線程就產生了。Java中每一個線程都是經過某個特定Thread對象的方法run()來完成其操做的,方法run( )稱爲線程體。 java

  下面是構建線程類幾種經常使用的方法: 編程

  public Thread() 網絡

  public Thread(Runnable target) 多線程

  public Thread(Runnable target, String name) ide

  public Thread(String name) oop

  參數target是一個實現Runnable接口的實例,它的做用是實現線程體的run()方法。目標target可爲null,表示由自己實例來執行線程。name參數指定線程名字,但沒有指定的構造方法,線程的名字是JVM分配的,例如JVM指定爲thread-一、thread-2等名字。 性能

  一、Java中的實現線程體方式1 測試

  在Java中有兩種方法實現線程體:一是繼承線程類Thread,二是實現接口Runnable。下面咱們先看看繼承線程類Thread方式。 this

  若是採用第1種方式,它繼承線程類Thread並重寫其中的方法 run(),在初始化這個類實例的時候,目標target可爲null,表示由本實例來執行線程體。因爲Java只支持單重繼承,用這種方法定義的類不能再繼承其餘父類,例如代碼清單8-1,完整代碼請參考chapter8_1工程中chapter8_1代碼部分。

  【代碼清單8-1】

public class chapter8_1 extends Thread {

    boolean isRunning = true;

    int timer = 0;

    /**
     * 線程體代碼
     */
    @Override
    public void run() {
        while (isRunning) {
            try {
                Thread.currentThread().sleep(1000);
                timer++;
                System.out.println("逝去了 "+timer+" 秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {

        chapter8_1 t1 = new chapter8_1();

        t1.start();
        System.out.println("計時器啓動...");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            String line = br.readLine();
            if (line.equalsIgnoreCase("1")) {
                t1.isRunning = false;
                /*t1.stop();*/
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    }

  在main主方法中經過new chapter8_1()建立子線程,並經過t1.start()方法啓動子線程,main主方法所在線程爲主線程,主線程負責管理其餘的子線程。本例進程、主線程和子線程之間的關係如圖8-5所示。

  子線程啓動以後就開始調用run()方法,run()是一個線程體,咱們在子線程中處理事情就是在這裏編寫代碼實現的。本案例中子線程要作的事情就是:休眠1s,計時器加1,再反覆執行。Thread.currentThread().sleep(1000)就是休眠1s。

  爲了可以中止線程,咱們在主線程中增長了一個標識,經過在控制檯輸入一個字符

  「1」來改變該標識t1.isRunning = false,從而結束這個線程。

 注意:

  事實上線程中有一個stop()方法也能夠中止線程,可是因爲這種方法會產生線程死鎖問題,因此在新版JDK中已經廢止了,它的替代解決方法就是增長標識,就是咱們在本例中採用的方案。

  不少人以爲線程難理解,主要有兩個問題:

  線程休眠,既然線程已經休眠了,程序的運行速度還能提升嗎?

  線程體通常都進行死循環,既然線程死循環,程序就應該死掉了,就會沒有反應。

  1.關於線程休眠問題

  對線程休眠問題頭痛的讀者,其實仍是在用單線程的思惟模式考慮問題,多數狀況下咱們的PC都是單CPU的,某個時間點只能有一個線程運行。所謂多線程就是多個線程交替執行就好像同時運行似的。所以,休眠當前線程能夠交出CPU控制權,讓其餘的線程有機會運行,多個線程之間只有交替運行效率纔是最高的,這就像咱們開車過十字路口,只有我等等,讓你先過,你再等等讓他先過,才能保證最高效率,不然就會形成交通系統崩潰,對線程狀況也是同樣的。所以,多線程中線程的休眠是程序運行的最有效方式。

  2.關於線程體死循環問題

  在單線程中若是是死循環,程序應就會死掉,沒有反應,可是多線程中線程體(run方法)中的死循環,能夠保證線程一直運行,若是不循環線程,則運行一次就中止了。在上面的例子中線程體運行死循環,能夠保證線程一直運行,每次運行都休眠1s,而後喚醒,再而後把時間信息輸出到控制檯。因此,線程體死循環是保證子線程一直運行的前提。因爲是子線程它不會堵塞主線程,就不會感受到程序死掉了。可是須要注意的是有時咱們確實執行一次線程體,就不須要循環了。

  程序運行後開始啓動線程,線程啓動後就計算逝去的時間,每過1s將結果輸出到控制檯。當輸入1字符後線程中止,程序終止。

 Java中的實現線程體方式2

  上面介紹繼承Thread方式實現線程體,下面介紹另外一種方式,這種方式是提供一個實現接口Runnable的類做爲一個線程的目標對象,構造線程時有兩個帶有Runnable target參數的構造方法:

  Thread(Runnable target);

  Thread(Runnable target, String name)。

  其中的target就是線程目標對象了,它是一個實現Runnable的類,在構造Thread類時候把目標對象(實現Runnable的類)傳遞給這個線程實例,由該目標對象(實現Runnable的類)提供線程體run()方法。這時候實現接口Runnable的類仍然能夠繼承其餘父類。

 請參看代碼清單8-2,這是一個Java AWT的窗體應用程序,完整代碼請參考chapter8_2工程中chapter8_2_1代碼部分。

  【代碼清單8-2】

public class chapter8_2_1 extends Frame implements ActionListener, Runnable {

    private Label label;
    private Button button1;
    private Thread clockThread;
    private boolean isRunning = false;
    private int timer = 0;

    public chapter8_2_1() {
        button1 = new Button("結束計時");
        label = new Label("計時器啓動...");
        button1.addActionListener(this);
        setLayout(new BorderLayout());
        add(button1, "North");
        add(label, "Center");
        setSize(320, 480);
        setVisible(true);

        clockThread = new Thread(this);
        /* 線程體是Clock對象自己,線程名字爲"Clock" */
        clockThread.start(); /* 啓動線程 */
        isRunning = true;
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        isRunning = false;
    }

    @Override

    public void run() {
        while (isRunning) {
            try {
                Thread.currentThread().sleep(1000);
                timer++;
                label.setText("逝去了 " + timer + " 秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String args[]) {
        chapter8_2_1 a = new chapter8_2_1();
    }

}

  其中關於Java AWT知識本書就不在這裏介紹了,有興趣的讀者能夠本身看看相關書籍。在本例中構建AWT窗體的應用程序方式是繼承Frame類。採用第1種方式——繼承方式實現線程體是不能夠的,由於Java是單繼承的,這個類不能既繼承Frame又繼承Thread。應該採用第2種方式——實現Runnable接口方式。Runnable接口也有一個run()方法,它是實現線程體方法,其代碼處理與上一節是同樣。須要注意的是,在第2種方法中,建立了一個Thread成員變量clockThread,才用構造方法new Thread(this)建立一個線程對象,其中建立線程使用的構造方法是Thread(Runnable target),其中的this就是表明本實例,它是一個實現了Runnable接口的實現類。

  程序運行結果如圖8-7所示,屏幕開始加載的時候線程啓動開始計算時間,1s更新一次UI,當單擊「結束計時」按鈕時,中止計時。

 Java中的實現線程體方式3

  實現線程體方式3是實現線程體方式2的變種,本質上仍是實現線程體方式2,可是在Android應用開發中常常採用第3種方式。下面咱們看第3種方式的計時器代碼清單8-3,完整代碼請參考chapter8_2工程中 chapter8_2_2代碼部分。

  【代碼清單8-3】

public class chapter8_2_2 extends Frame implements ActionListener {

    private Label label;
    private Button button1;
    private Thread clockThread;

    private boolean isRunning = false;
    private int timer = 0;

    public chapter8_2_2() {
        button1 = new Button("結束計時");
        label = new Label("計時器啓動...");
        button1.addActionListener(this);
        setLayout(new BorderLayout());
        add(button1, "North");
        add(label, "Center");
        setSize(320, 480);
        setVisible(true);

        /* 線程體是Clock對象自己,線程名字爲"Clock" */
        clockThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        Thread.currentThread().sleep(1000);
                        timer++;
                        label.setText("逝去了 " + timer + " 秒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        clockThread.start(); /* 啓動線程 */
        isRunning = true;
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        isRunning = false;
    }

    public static void main(String args[]) {
        chapter8_2_2 a = new chapter8_2_2();
    }

    }

  與第2種方式比較,咱們發現Frame類再也不實現Runnable接口了,而是在實例化Thread類的時候,定義了一個實現Runnable接口的匿名內部類:

clockThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        Thread.currentThread().sleep(1000);
                        timer++;
                        label.setText("逝去了 " + timer + " 秒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    });

  有關Java多線程的內容還有不少,例如線程優先級、線程同步等,因爲這些內容與本書關係不是很緊密,因此再也不介紹了,有關其餘的線程知識能夠參考Java方面的書籍。接下來介紹一下Android中的線程。

  Android中的線程

  在Android平臺中多線程應用很普遍,在UI更新、遊戲開發和耗時處理(網絡通訊等)等方面都須要多線程。Android線程涉及的技術有:Handler;Message;MessageQueue;Looper;HandlerThread。

  Android線程應用中的問題與分析

  爲了介紹這些概念,咱們把計時器的案例移植到Android系統上,按照在Frame方式修改以後的代碼清單8-4,完整代碼請參考chapter8_3工程中 chapter8_3代碼部分。

  【代碼清單8-4】

public class chapter8_3 extends Activity {

    private String TAG = "chapter8_3";
    private Button btnEnd;
    private TextView labelTimer;
    private Thread clockThread;
    private boolean isRunning = true;
    private int timer = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        btnEnd = (Button) findViewById(R.id.btnEnd);
        btnEnd.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                isRunning = false;
            }
        });

        labelTimer = (TextView) findViewById(R.id.labelTimer);

        /* 線程體是Clock對象自己,線程名字爲"Clock" */
        clockThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        Thread.currentThread().sleep(1000);
                        timer++;
                        labelTimer.setText("逝去了 " + timer + " 秒");
                        Log.d(TAG, "lost  time " + timer);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        });

        clockThread.start(); /* 啓動線程 */

    }
    }

  程序打包運行結果出現了異常

系統拋出的異常信息是「Only the original thread that created a view hierarchy can touch its views」,在Android中更新UI處理必須由建立它的線程更新,而不能在其餘線程中更新。上面的錯誤緣由就在於此。

  如今分析一下上面的案例,在上面的程序中有兩個線程:一個主線程和一個子線程,它們的職責如圖8-10所示。

  因爲labelTimer是一個UI控件,它是在主線程中建立的,可是它卻在子線程中被更新了,更新操做在clockThread線程的run()方法中實現

/* 線程體是Clock對象自己,線程名字爲"Clock" */
clockThread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (isRunning) {
            try {
                Thread.currentThread().sleep(1000);
                timer++;
                labelTimer.setText("逝去了 " + timer + " 秒");
                Log.d(TAG, "lost  time " + timer);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});

  這樣的處理違背了Android多線程編程規則,系統會拋出異常「Only the original thread that created a view hierarchy can touch its views」。

  要解決這個問題,就要明確主線程和子線程的職責。主線程的職責是建立、顯示和更新UI控件、處理UI事件、啓動子線程、中止子線程;子線程的職責是計算逝去的時間和向主線程發出更新UI消息,而不是直接更新UI。

 主線程的職責是顯示UI控件、處理UI事件、啓動子線程、中止子線程和更新UI,子線程的職責是計算逝去的時間和向主線程發出更新UI消息。可是新的問題又出現了:子線程和主線程如何發送消息、如何通訊呢?

  在Android中,線程有兩個對象—消息(Message)和消息隊列(MessageQueue)能夠實現線程間的通訊。下面再看看修改以後的代碼清單8-5,完整代碼請參考chapter8_4工程中chapter8_4代碼部分。

  【代碼清單8-5】

public class chapter8_4 extends Activity {

    private String TAG = "chapter8_3";
    private Button btnEnd;
    private TextView labelTimer;
    private Thread clockThread;
    private boolean isRunning = true;
    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btnEnd = (Button) findViewById(R.id.btnEnd);
        btnEnd.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                isRunning = false;
            }
        });

        handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case 0:
                    labelTimer.setText("逝去了 " + msg.obj + " 秒");
                }
            }

        };

        labelTimer = (TextView) findViewById(R.id.labelTimer);

        /* 線程體是Clock對象自己,線程名字爲"Clock" */
        clockThread = new Thread(new Runnable() {
            @Override

            public void run() {
                int timer = 0;
                while (isRunning) {
                    try {
                        Thread.currentThread().sleep(1000);
                        timer++;
                        /* labelTimer.setText("逝去了 " + timer + " 秒"); */
                        Message msg = new Message();
                        msg.obj = timer;
                        msg.what = 0;
                        handler.sendMessage(msg);
                        Log.d(TAG, "lost  time " + timer);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        clockThread.start(); /* 啓動線程 */

    }

  有的時候爲了將Android代碼變得更加緊湊,把線程的建立和啓動編寫在一條語句中,以下面chapter8_5的代碼片斷。代碼清單8-6所示,完整代碼請參考chapter8_5工程中 chapter8_5代碼部分。

  【代碼清單8-6】

new Thread() {
        @Override
        public void run() {
            int timer = 0;
            while (isRunning) {
                ry {
                    Thread.currentThread().sleep(1000);
                    timer++;
                    / labelTimer.setText("逝去了 " + timer + " 秒");
                    Message msg = new Message();
                    msg.obj = timer;
                    msg.what = 0;
                    handler.sendMessage(msg);
                    Log.d(TAG, "lost  time " + timer);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }.start();

  chapter8_5代碼看起來有些糊塗吧?chapter8_4和chapter8_5建立線程的區別是:chapter8_4採用Thread(Runnable target)構造方法建立一個線程,須要提供一個Runnable接口對象,須要提供的參數是實現了Runnable接口的匿名內部類對象。chapter8_5採用Thread()構造方法建立一個線程,在這裏採用了簡便的編程方法,直接新建一個Thread類,同時重寫run()方法。

  chapter8_5編程方法雖然晦澀難懂,並且違背了Java編程規範,程序結構也比較混亂,但倒是Android習慣寫法,這主要源於Android對於減小字節碼的追求。究竟這兩種方式在性能上有多少差異呢?誠實地講我沒有作過測試和求證,在我看來就上面的程序而言它們之間不會有太大差異,因爲本書要儘量遵照Java編程規範和Android的編程習慣,所以本書中兩種編程方式都會採用,若是給你們帶來不便敬請諒解。

  運行模擬器結果如圖8-1所示,加載屏幕後立刻開始計時,也能夠單擊「中止計時」按鈕來中止計時。

相關文章
相關標籤/搜索