服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

多线程在java me应用程序中的使用

    多线程技术是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窗口中得到如下警告:

多线程在java me应用程序中的使用(图一)

    图1

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

    这就是因为没有使用多线程造成的。下面,就来看看如何使用多线程来解决此问题。

 

[nextpage]


    新建类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所示。

多线程在java me应用程序中的使用(图二)

  图2

多线程与拍照

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

多线程在java me应用程序中的使用(图三)

  图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);
}

 

[nextpage]

    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所示。

多线程在java me应用程序中的使用(图四)

  图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所示:

多线程在java me应用程序中的使用(图五)

  图5

 

[nextpage]

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所示:

多线程在java me应用程序中的使用(图六)

  图6

总  结

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

扫描关注微信公众号