首頁

2013年6月9日 星期日

**Android开发中的多线程编程技术*~

**Android开发中
的多线程编程技术*~

*多线程这个令人生畏的“洪水猛兽”,很多人谈起多线程都心存畏惧。
在Android开发过程中,多线程真的很难吗?多线程程序的“麻烦”源于 
它很抽象、与单线程程序运行模式不同,但只要掌握了它们的区别, 
编写多线程程序就会很容易了。下面让我们集中精力开始学习吧*

*多线程案例——计时器*
*举一个计时器的案例,因为计时器案例是多线程的经典应用。
  这个案例中,屏幕启动之后,进入如  
图8-1所示的界面。*
*屏幕上有一个文本框用于显示逝去的时间,
此外还有一个“停止计时”按钮。案例的用
例图如图8-2所示。

  
  ▲图8-1 计时器界面
  
 








 ▲图8-2 计时器用例图
  能够在屏幕上“实时地显示”时间的
流逝,单线程程序是无法实现的,必须要
多线程程序才可以实现,即便有些计算机
语言可以通过封装好的类实现这一功能,
但从本质上讲这些封装好的类就是封装
了一个线程。
  综上所述,完成本案例用到的
知识及技术如下:
  1)进程和线程的概念;
  2)Java中的线程,在Java中创建
线程的方式;
  3)Android中的线程,包括:Message
、Handler、Looper和HandlerThread等概念。
  线程究竟是什么?在Windows
操作系统出现之前,个人计算机上的
操作系统都是单任务系统,只有在大型
计算机上才具有多任务和分时设计。
Windows、Linux操作系统的出现,把原
本只在大型计算机才具有的优点,带到
了个人计算机系统中。
  进程概念
  一般可以在同一时间内执行多个程序
的操作系统都有进程的概念。一个进程就
是一个执行中的程序,而每一个进程都有
自己独立的一块内存空间、一组系统资源。
在进程的概念中,每一个进程的内部数据
和状态都是完全独立的。在Windows操作
系统下我们可以通过〈Ctrl+Alt+Del〉
组合键查看进程,在UNIX和Linux操作
系统下是通过PS命令查看进程的。
打开Windows当前运行的进程,
如图8-3所示。
  Android开发中的多线程编程技术

















  ▲图8-3 Windows操作系统进程
  在Windows操作系统中一个进程就是
一个exe或dll程序,它们相互独立,互相
也可以通信,在Android操作系统中进程
间的通信应用也是很多的。
  线程概念
  多线程指的是在单个程序中可以同时
运行多个不同的线程,执行不同的任务。
多线程意味着一个程序的多行语句可以看
上去几乎在同一时间内同时运行。
  线程与进程相似,是一段完成某个特
定功能的代码,是程序中单个顺序的流
控制。但与进程不同的是,同类的多个
线程共享一块内存空间和一组系统资源,
所以系统在各个线程之间切换时,资源占
用要比进程小得多,正因如此,线程也被
称为轻量级进程。一个进程中可以包含
多个线程。图8-4所示是计时器程序进程
和线程之间的关系,主线程负责管理子
线程,即子线程的启动、挂起、停止等操作。
  Android开发中的多线程编程技术
 








 ▲图8-4 进程和线程关系

  Java中的线程
  Java的线程类是java.lang.Thread类。
当生成一个Thread类的对象之后,一个新
的线程就产生了。Java中每个线程都是
通过某个特定Thread对象的方法run()来
完成其操作的,方法run( )称为线程体。
  下面是构建线程类几种常用的方法:
  public Thread()
  public Thread(Runnable target)
  public Thread(Runnable target,
 String name)
  public Thread(String name)
  参数target是一个实现Runnable接口的
实例,它的作用是实现线程体的run()方法。
目标target可为null,表示由本身实例来
执行线程。name参数指定线程名字,但
没有指定的构造方法,线程的名字
是JVM分配的,例如JVM指定
为thread-1、thread-2等名字。
  1、Java中的实现线程体方式1
  在Java中有两种方法实现线程体:
一是继承线程类Thread,二是实现
接口Runnable。
下面我们先看看继承线程类Thread方式。
  如果采用第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) findView
ById(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所示,加载屏幕后
马上开始计时,也可以单击“停止计时”按钮
来停止计时。*


&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

沒有留言:

張貼留言


if you like make fds, wellcome you here~~anytime***

my free place for everyones who want the good software,

come & download them~ wellcome!!