日志 教程
当前位置: 教程  >  游戏开发  >  手机游戏开发  >  正文

多线程在JAVA ME应用程序中的使用

作者:吴镇风 发表于 2011/5/25 15:41:10     评论(9)     阅读(7778)     

多线程技术是JAVA ME中的关键技术,应用十分频繁,尤其是在游戏中。但是对于新手来说,又容易忽略或错误的使用多线程,导致程序堵塞,而无法响应用户的输入请求。
    由于笔者对于游戏开发不是十分了解,所以本文将仅就多线程技术在JAVA ME应用程序中的使用展开讨论。本文主要包含如下部分:

  •          多线程与联网
  •          多线程与拍照
  •          Timer与TimerTask

多线程与联网

   手机中,所有的MIDlet程序都是由Application Manager Software(AMS)管理的。当MIDlet初始化后,AMS就会调用MIDlet的startApp()方法,此时MIDlet就进入了 Acitive状态。在JAVA ME中有些操作可能会导致程序堵塞,比如连接网络等。如果这些操作与主程序在同一个主线程中完成,那么由于堵塞会造成程序长时间无法返回,也就无法响应用户的其他操作了。所以,如果我们在commandAction()中进行了联网的操作,则会造成如上所述的情况。
   下面,将通过一个例子来演示如上的情况,并使用多线程最终解决此问题。这是一个“Echo Message”实例,手机端向服务器端发送一条消息,服务器得到此消息后直接返回给手机端。
   首先,创建一个NetworkConnection类来封装联网相关的操作,这样,MIDlet中只需调用此类中的方法就可以完成联网的操作。代码如下:

以下是引用片段:
/* 
 * NetworkConnection.java 
 * 
 * Created on 2006年7月20日, 下午2:54 
 * 
 */ 
package nju.hysteria.thread.connection; 
import java.io.DataInputStream; 
import java.io.DataOutputStream; 
import java.io.IOException; 
import javax.microedition.io.Connector; 
import javax.microedition.io.HttpConnection; 
/** 
 * 
 * @author Magic 
 */ 
public class NetworkConnection { 
    private static final String URL = "http://localhost:8080/thread/"; 
    private HttpConnection httpConnection; 
    private String message; 
     
    public NetworkConnection(String message) { 
        this.message = message; 
        connect(); 
    } 
     
    /** 
     * Connect to web server. 
     * 
     */ 
    public void connect(){ 
        try { 
            httpConnection = (HttpConnection) Connector.open(URL); 
            httpConnection.setRequestMethod(HttpConnection.POST); 
        } catch (IOException ex) { 
            System.out.println("Can not open connection!"); 
            ex.printStackTrace(); 
        } 
    } 
     
     
    /** 
     * Send message to server. 
     * @throws java.io.IOException  
     */ 
    public void sendMessage() throws IOException{ 
        DataOutputStream out = httpConnection.openDataOutputStream(); 
        out.writeUTF(message); 
        out.close(); 
    } 
     
    /** 
     * Receive message from server. 
     * @throws java.io.IOException  
     * @return  
     */ 
    public String receiveMessage() throws IOException { 
        DataInputStream in = httpConnection.openDataInputStream(); 
        String message = in.readUTF(); 
        in.close(); 
        return message; 
    } 
     
    /** 
     * Close connection. 
     */ 
    public void close(){ 
        if(httpConnection!=null){ 
            try { 
                httpConnection.close(); 
            } catch (IOException ex) { 
                ex.printStackTrace(); 
            } 
        } 
    } 
     
}
    构造函数的参数是将要被发送的消息。服务器端的代码在此不再列出,详细请见本文的源代码。
    接着,我们写一个MIDlet调用类中的方法。MalConnectionMidlet在commandAction()方法中直接调用NetworkConnection中的方法,而没有重新创建一个线程。代码如下:

以下是引用片段:
/* 
 * MalConnectionMidlet.java 
 * 
 * Created on 2006年7月20日, 下午2:53 
 */ 
package nju.hysteria.thread.connection; 
import java.io.IOException; 
import javax.microedition.midlet.*; 
import javax.microedition.lcdui.*; 
/** 
 * 
 * @author  Magic 
 * @version 
 */ 
public class MalConnectionMidlet extends MIDlet implements CommandListener { 
    private Display display; 
    private TextBox text; 
    private Command showCommand; 
     
    public MalConnectionMidlet(){ 
        display = Display.getDisplay(this); 
        text = new TextBox("Message","请使用‘问候’命令发送消息",100,TextField.ANY); 
        showCommand = new Command("问候",Command.SCREEN,1); 
        text.addCommand(showCommand); 
        text.setCommandListener(this); 
    } 
     
    public void startApp() { 
        display.setCurrent(text); 
    } 
     
    public void pauseApp() { 
    } 
     
    public void destroyApp(boolean unconditional) { 
    } 
    public void commandAction(Command command, Displayable displayable) { 
        if(command==showCommand){ 
            /** 
             *  在当前的线程中直接进行联网操作,造成程序堵塞。 
       */ 
            String message = null; 
             
            NetworkConnection connection = new NetworkConnection("Hello World"); 
            try { 
                connection.sendMessage(); 
                message = connection.receiveMessage(); 
                connection.close(); 
            } catch (IOException ex) { 
                ex.printStackTrace(); 
            } 
            text.setString(message); 
        } 
    } 
}
    当用户按下“问候”命令时,就会向服务器发送“Hello World”的消息,然后再得到服务器返回的消息,并显示在TextBox中。
    运行程序,如图1所示。当用户按下“问候”命令后,程序就僵死了,并在Console窗口中得到如下警告:

    图1

以下是引用片段:
Warning: To avoid potential deadlock, operations that may block, such as  
 networking, should be performed in a different thread than the  
 commandAction() handler.

    这就是因为没有使用多线程造成的。下面,就来看看如何使用多线程来解决此问题。
    新建类NetworkThread,它继承在Thread,并将原先commandAction()中发送,接受消息的操作移到此类中完成。代码如下:

以下是引用片段:
/* 
 * NetworkThread.java 
 * 
 * Created on 2006年7月20日, 下午4:16 
 * 
 */ 
package nju.hysteria.thread.connection; 
import java.io.IOException; 
import javax.microedition.lcdui.TextBox; 
/** 
 * 
 * @author Magic 
 */ 
public class NetworkThread extends Thread { 
    private NetworkConnection connection; 
    private TextBox text; 
     
    public NetworkThread(TextBox text) { 
        super(); 
        this.text = text; 
    } 
    public void run() { 
        String message = null; 
             
        connection = new NetworkConnection("Hello World"); 
        try { 
            connection.sendMessage(); 
            message = connection.receiveMessage(); 
            connection.close(); 
        } catch (IOException ex) { 
            ex.printStackTrace(); 
        } 
        text.setString(message); 
    } 
     
}
    同时,修改原先的MIDlet,得到新的MIDlet:

以下是引用片段:
/* 
 * ConnectionMidlet.java 
 * 
 * Created on 2006年7月20日, 下午2:53 
 */ 
package nju.hysteria.thread.connection; 
import java.io.IOException; 
import javax.microedition.midlet.*; 
import javax.microedition.lcdui.*; 
/** 
 * 
 * @author  Magic 
 * @version 
 */ 
public class ConnectionMidlet extends MIDlet implements CommandListener { 
    private Display display; 
    private TextBox text; 
    private Command showCommand; 
     
    public ConnectionMidlet(){ 
        display = Display.getDisplay(this); 
        text = new TextBox("Message","请使用‘问候’命令发送消息",100,TextField.ANY); 
        showCommand = new Command("问候",Command.SCREEN,1); 
        text.addCommand(showCommand); 
        text.setCommandListener(this); 
    } 
     
    public void startApp() { 
        display.setCurrent(text); 
    } 
     
    public void pauseApp() { 
    } 
     
    public void destroyApp(boolean unconditional) { 
    } 
    public void commandAction(Command command, Displayable displayable) { 
        if(command==showCommand){ 
            /** 
             * 创建新的线程完成联网操作 
             */ 
            (new NetworkThread(text)).start(); 
        } 
    } 
}
    此时,在commandAction()中,我们创建了新的线程来完成消息的发送和接受。运行程序,可以成功接受到返回的消息,如图2所示。

  图2

多线程与拍照

    同样,在移动多媒体API(JSR 135)的使用过程中也需要应用到多线程。利用手机摄像头拍照的操作也会引起的堵塞,因此当在commandAction()中调用拍照操作时,若未开辟新线程来处理,同样也会造成程序没有响应。让我们通过一个例子来观察一下吧。
    这是一个很简单的摄像头程序,首先程序将会调用摄像头取景,然后当用户按下“拍照”命令后就捕捉当前的画面并显示给用户。图3是程序的UML类图。

  图3

    MalCameraMidlet和CameraMidlet分别是错误和正确的MIDlet,它们都创建一个CameraView(用于显示摄像头画面)的对象,唯一的不同在于前者创建MalCamera的对象,后者创建Camera的对象(MalCamera和Camera都是CameraView的子类)。SnapShot则是将摄像头捕捉到的图像显示给用户的类。
    首先,我们还是来看一下未使用多线程,而造成程序没有响应的情况。如下是MalCameraMidlet类的代码,其中保存着一个指向CameraView的引用,并在startApp()中创建了MalCamera对象。

以下是引用片段:
/** 
 * MalCameraMidlet.java 
 *  
 */ 
package nju.hysteria.thread.camera; 
import javax.microedition.lcdui.Display; 
import javax.microedition.midlet.MIDlet; 
/** 
 * This MIDlet create the mal camera view, so the program 
 * will block after calling Capture command. 
 * @author Magic 
 * 
 */ 
public class MalCameraMidlet extends MIDlet { 
 protected Display display; 
 private CameraView camera; 
  
 public MalCameraMidlet() { 
  super(); 
  display = Display.getDisplay(this); 
 } 
 protected void startApp() { 
  camera = new MalCamera(this); 
  display.setCurrent(camera); 
 } 
  
       /** 
        * Show current camera. 
        */ 
 public void showCamera(){ 
  display.setCurrent(camera); 
 } 
  
 protected void pauseApp() { 
 } 
 protected void destroyApp(boolean arg0) { 
 } 
}
    CameraView是一个抽象类,是两个显示Camera的类的父类。它负责显示Camera,并将commandAction()方法留给子类实现,代码如下:

以下是引用片段:
/** 
 * CameraView.java 
 */ 
package nju.hysteria.thread.camera; 
import java.io.IOException; 
import javax.microedition.lcdui.Command; 
import javax.microedition.lcdui.CommandListener; 
import javax.microedition.lcdui.Displayable; 
import javax.microedition.lcdui.Form; 
import javax.microedition.lcdui.Item; 
import javax.microedition.media.Manager; 
import javax.microedition.media.MediaException; 
import javax.microedition.media.Player; 
import javax.microedition.media.control.VideoControl; 
import javax.microedition.midlet.MIDlet; 
/** 
 * This is an abstract class, which display camera to user. 
 * @author Magic 
 * 
 */ 
public abstract class CameraView extends Form implements CommandListener { 
 protected MIDlet midlet; 
 protected Player player; 
 protected VideoControl vc; 
 protected Command exitCommand; 
 protected Command captureCommand; 
  
 protected CameraView(MIDlet midlet){ 
  super("照相"); 
  this.midlet = midlet; 
   
  exitCommand = new Command("退出",Command.EXIT,0); 
  captureCommand = new Command("拍照",Command.SCREEN,1); 
   
  addCommand(exitCommand); 
  addCommand(captureCommand); 
   
  setCommandListener(this); 
   
  /** 
   * Create camera player and control. 
   */ 
  try { 
   player = Manager.createPlayer("capture://video"); 
   player.realize(); 
    
   vc = (VideoControl)player.getControl("VideoControl"); 
   append((Item)vc.initDisplayMode(VideoControl.USE_GUI_PRIMITIVE,null)); 
   player.start(); 
  } catch (IOException e) { 
   e.printStackTrace(); 
  } catch (MediaException e) { 
   e.printStackTrace(); 
  } 
 } 
  
 public abstract void commandAction(Command cmd,Displayable displayable); 
}
    MalCamera和Camera都继承了CameraView类,并分别实现了commandAction()方法。先来看一下MalCamera:

以下是引用片段:
/** 
 * MalCamera.java 
 */ 
package nju.hysteria.thread.camera; 

import javax.microedition.lcdui.Command; 
import javax.microedition.lcdui.Displayable; 
import javax.microedition.media.MediaException; 
import javax.microedition.midlet.MIDlet; 
/** 
 * This class display the mal camera. In commandAction(), 
 * for capture command, just get the data without create a  
 * new thread, and thus cause program blocked. 
 * @author Magic 
 * 
 */ 
public class MalCamera extends CameraView { 

  
 public MalCamera(MIDlet midlet){ 
  super(midlet); 
 } 
  
 public void commandAction(Command cmd, Displayable displayable) { 
  if(cmd==exitCommand){ 
   try { 
    player.stop(); 
   } catch (MediaException e) { 
    e.printStackTrace(); 
   } 
   player.close(); 
   ((MalCameraMidlet)midlet).destroyApp(false); 
   midlet.notifyDestroyed(); 
  }else if(cmd==captureCommand){ 
   // Do not handle in a new thread. 
   try { 
    byte[] data = vc.getSnapshot(null); 
    new SnapShot(midlet,data); 
   } catch (MediaException e) { 
    e.printStackTrace(); 
   } 
  } 
 } 
}
    其中SnapShot是显示捕捉到的图像的界面,详细请看文后的源代码,这里不再赘述。
    现在运行MalCameraMidlet,按下“拍照”命令后,出现询问是否记录图像的提示,但是,当你按下Yes后程序就再也没有响应了,如图4所示。

  图4

    查看控制台窗口,得到如下提示:

以下是引用片段:
Warning: To avoid potential deadlock, operations that may block, such as  
 networking, should be performed in a different thread than the  
 commandAction() handler.

    同样,还是由于没有创建新的线程进行处理的原因。下面,我们就把处理的代码放到新的线程中来完成,看看情况如何。如下是修改过的MalCamera代码,Camera.java:

以下是引用片段:
/** 
 * Camera.java 
 */ 
package nju.hysteria.thread.camera; 
import javax.microedition.lcdui.Command; 
import javax.microedition.lcdui.Displayable; 
import javax.microedition.media.MediaException; 
import javax.microedition.midlet.MIDlet; 
/** 
 * This class displays camera. And do the right thing  
 * for capture command, putting code in a new thread. 
 * @author Magic 
 * 
 */ 
public class Camera extends CameraView { 
 public Camera(MIDlet midlet){ 
  super(midlet); 
 } 
  
 public void commandAction(Command cmd, Displayable displayable) { 
  if(cmd==exitCommand){ 
   try { 
    player.stop(); 
   } catch (MediaException e) { 
    e.printStackTrace(); 
   } 
   player.close(); 
   ((CameraMidlet)midlet).destroyApp(false); 
   midlet.notifyDestroyed(); 
  }else if(cmd==captureCommand){ 
   // Handle in a new thread. 
   new Thread(){ 
    public void run(){ 
     try { 
      byte[] data = vc.getSnapshot(null); 
      new SnapShot(midlet,data); 
     } catch (MediaException e) { 
      e.printStackTrace(); 
     } 
    } 
   }.start(); 
  } 
 } 
}
    同样,我们也需要在MIDlet中创建Camera的对象,而不是MalCamera。CameraMidlet的代码如下:

以下是引用片段:
/** 
 * CameraMidlet.java 
 */ 
package nju.hysteria.thread.camera; 
import javax.microedition.lcdui.Display; 
import javax.microedition.midlet.MIDlet; 
/** 
 * The correct MIDlet. 
 * @author Magic 
 * 
 */ 
public class CameraMidlet extends MIDlet { 
 protected Display display; 
 private CameraView camera; 
  
 public CameraMidlet() { 
  super(); 
  display = Display.getDisplay(this); 
 } 
 protected void startApp() { 
  camera = new Camera(this); 
  display.setCurrent(camera); 
 } 
       /** 
        * Show current camera. 
        */ 
 public void showCamera(){ 
  display.setCurrent(camera); 
 } 
  
 protected void pauseApp() { 
 } 
 protected void destroyApp(boolean arg0) { 
 } 
}
    运行CameraMidlet,按下“拍照”命令,这次程序没有堵塞,我们可以得到捕捉到的图片。如图5所示:

  图5

Timer与TimerTask

    联网和拍照这两种情况都需要程序员创建新的线程来完成任务,并且这种做法对于程序员来说是显式的,即通过直接使用Thread类或Runnable接口来直接创建新线程。在MIDP的API中同样提供了隐式的方式来创建新线程,以方便程序员的编程。这就是TimerTask类,它实现了Runnable接口,用户只需创建一个继承它的类,并且实现run()方法,以此来创建新线程,而无需显示的继承Thread或Runnable。
    当然,TimerTask的优点不仅于此,从它的名字来看,可以认为它是一个在特定时间执行的任务。run()方法中代码就是这任务,那么怎么控制其在特定时间执行呢?这就需要Timer这个类的帮助了。顾名思义,Timer是一个定时器,通过调用它的多个schedule(...)方法中的一个,可以控制在特定的时间,或每隔一定时间执行TimerTask。具体的方法介绍请看JDK文档。
    TimerTask和Timer经常一起使用,比如在显示时间,倒计时和显示欢迎界面时会经常用到。下面,就通过一个实例来介绍这两个的用法。
    这是一个计时的程序,程序从0秒开始,用户可以随时暂停或继续计时。程序是通过Timer和TimerTask来完成的,包含三个类:ClockMidlet,ClockCanvas和Clock。首先来看一下本程序最主要的类ClockCanvas:

以下是引用片段:
/** 
 * ClockCanvas.java 
 */ 
package nju.hysteria.thread.clock; 
import java.util.Timer; 
import javax.microedition.lcdui.Canvas; 
import javax.microedition.lcdui.Command; 
import javax.microedition.lcdui.CommandListener; 
import javax.microedition.lcdui.Displayable; 
import javax.microedition.lcdui.Font; 
import javax.microedition.lcdui.Graphics; 
/** 
 * This class display time to user, and also start the timer 
 * to update time each second. 
 * @author Magic 
 * 
 */ 
public class ClockCanvas extends Canvas implements CommandListener { 
 private ClockMidlet midlet; 
 private Command exitCommand; 
 private Command stopCommand; 
 private Command resumeCommand; 
 private long second; 
 private int x; 
 private int y; 
  
 // Timer 
 private Timer timer;
 
  
 public ClockCanvas(ClockMidlet midlet){ 
  this.midlet = midlet; 
  exitCommand = new Command("退出",Command.EXIT,0); 
  stopCommand = new Command("停止",Command.SCREEN,1); 
  resumeCommand = new Command("继续",Command.SCREEN,1); 
   
  addCommand(exitCommand); 
  addCommand(stopCommand); 
  setCommandListener(this); 
   
  second = 0; 
  x = getWidth()/2 - 10; 
  y = getHeight()/2 - 10; 
   
  // Create timer and start it. 
  timer = new Timer(); 
  timer.schedule(new Clock(this),0,1000);
 
 } 
  
 /** 
  * Add one second. 
  * 
  */ 
 public void addSecond(){ 
  second++; 
 } 
  
 protected void paint(Graphics g) { 
  g.setColor(0,0,0); 
  g.fillRect(0,0,getWidth(),getHeight()); 
  g.setColor(255,0,0); 
  g.setFont(Font.getFont(Font.FACE_SYSTEM,Font.STYLE_PLAIN,Font.SIZE_LARGE)); 
  // Draw second. 
  g.drawString(String.valueOf(second),x,y,Graphics.LEFT|Graphics.TOP);
 
 } 
 public void commandAction(Command cmd, Displayable displayable) { 
  if(cmd==exitCommand){ 
   timer.cancel(); 
   midlet.destroyApp(false); 
   midlet.notifyDestroyed(); 
  }else if (cmd==stopCommand){ 
   timer.cancel(); 
   removeCommand(stopCommand); 
   addCommand(resumeCommand); 
  }else if (cmd==resumeCommand){ 
   timer = null; 
   timer = new Timer(); 
   timer.schedule(new Clock(this),0,1000);
 
   removeCommand(resumeCommand); 
   addCommand(stopCommand); 
  } 
 } 
}
    ClockCanvas继承了Canvas,用来向用户显示当前的秒数。注意构造函数中黑体的代码,创建了Timer,并且调用schedule()方法来设定运行任务的时间,第一个参数是TimerTask的对象,这里是Clock,它继承了TimerTask,我们将稍后讨论;第二个参数是运行的延迟,这里为0,也就是立刻执行;第三个参数是连续运行的时间间隔,这里为1000毫秒,也就是每隔1秒钟更新界面。
    注意commandAction()方法,在处理“停止”命令时,需要停止计时,此处调用timer.cancle()来取消计时器,所以界面将停止更新;当按下“继续”命令后,又需要继续开始计时,所以我们重新创建了Timer,因为原来的已经取消了,是不可用的了。
    接着就来看看Clock是如何来工作的,代码如下:

以下是引用片段:
/** 
 * Clock.java 
 */ 
package nju.hysteria.thread.clock; 
import java.util.TimerTask; 
/** 
 * Update the time. 
 * @author Magic 
 * 
 */ 
public class Clock extends TimerTask { 
 private ClockCanvas canvas; 
  
 public Clock(ClockCanvas canvas){ 
  this.canvas = canvas; 
 } 
  
 public void run() { 
  canvas.addSecond(); 
  canvas.repaint(); 
 } 
}

   非常简单,在run()方法中就是调用了ClockCanvas类中addSencond()方法来增加时间,同时调用repaint()来更新界面。从表面上看几乎没有多线程的痕迹,其实创建Timer的时候就相当于在后台创建了一个新的线程,它控制着TimerTask的执行,因此对于秒数的增加是在另一个线程的完成的,而主线程只负责更新显示。
   在加上下面的ClockMidlet就可以运行程序了。

以下是引用片段:
/** 
 * ClockMidlet.java 
 */ 
package nju.hysteria.thread.clock; 
import javax.microedition.lcdui.Canvas; 
import javax.microedition.lcdui.Display; 
import javax.microedition.midlet.MIDlet; 
/** 
 * Clock MIDlet. 
 * @author Magic 
 * 
 */ 
public class ClockMidlet extends MIDlet { 
 private Display display; 
 private Canvas clockCanvas; 
  
 public ClockMidlet() { 
  super(); 
  display = Display.getDisplay(this); 
 } 
 protected void startApp(){ 
  clockCanvas = new ClockCanvas(this); 
  display.setCurrent(clockCanvas); 
 } 
 protected void pauseApp() { 
 } 
 protected void destroyApp(boolean arg0) { 
 } 
}
   运行程序,将以秒为单位进行计时,如图6所示:

  图6

总  结

    以上介绍了多线程技术在联网,拍照中的应用,以及MIDP自带的TimerTask和Timer的使用方法。在实际编程过程中会遇到更多的使用多线程的情况,比如播放背景音乐等,但基本方法都是如此,这里不再赘述了。
    由于笔者水平有限,以上论述只是蜻蜓点水,未能深入讨论多线程的使用,特别是防止死锁,以及wait()/notify()的使用。

评论
显示
悄悄话
    李瑞
    2013-10-21 10:46
    王国云
    2013-09-12 23:10
    邓玉
    2013-04-11 20:17
    李铁
  • 李铁的评论:
  •   看的我眼睛都晕拉还是看不懂http://www.taoweiy.com/
    2012-04-09 20:31
    何文远
  • 何文远的评论:
  •   学习了。。。。。~~~~~~
    2012-02-23 23:05
    何文远
  • 何文远的评论:
  •   学习了。。。。。
    2012-02-23 23:04
    夏智豪
    2011-11-25 23:06
    李伟
  • 李伟的评论:
  •   高手1
    2011-08-29 20:19
    云天堂
    2011-05-25 15:45
汇众教育官网 | 联系方式 | 版权声明 | 友情链接
Copyright 2008© 汇众益智(北京)教育科技有限公司. All Rights Reserved
')京ICP备09092043号 京公网安备11010802009023号