熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

多線程在JAVA ME應用程序中的使用

2022-06-13   來源: Java高級技術 

  多線程技術是JAVA ME中的關鍵技術應用十分頻繁尤其是在游戲中但是對於新手來說又容易忽略或錯誤的使用多線程導致程序堵塞而無法響應用戶的輸入請求
    由於筆者對於游戲開發不是十分了解所以本文將僅就多線程技術在JAVA ME應用程序中的使用展開討論本文主要包含如下部分


         多線程與聯網
         多線程與拍照
         Timer與TimerTask

  多線程與聯網

  手機中所有的MIDlet程序都是由Application Manager Software(AMS)管理的當MIDlet初始化後AMS就會調用MIDlet的startApp()方法此時MIDlet就進入了Acitive狀態在JAVA ME中有些操作可能會導致程序堵塞比如連接網絡等如果這些操作與主程序在同一個主線程中完成那麼由於堵塞會造成程序長時間無法返回也就無法響應用戶的其他操作了所以如果我們在commandAction()中進行了聯網的操作則會造成如上所述的情況
   下面將通過一個例子來演示如上的情況並使用多線程最終解決此問題這是一個Echo Message實例手機端向服務器端發送一條消息服務器得到此消息後直接返回給手機端
   首先創建一個NetworkConnection類來封裝聯網相關的操作這樣MIDlet中只需調用此類中的方法就可以完成聯網的操作代碼如下


   以下是引用片段
/*
 * NetworkConnectionjava
 *
 * Created on  下午:
 *
 */
package njunnection;
import javaioDataInputStream;
import javaioDataOutputStream;
import javaioIOException;
import javaxmicroeditionioConnector;
import javaxmicroeditionioHttpConnection;
/**
 *
 * @author Magic
 */
public class NetworkConnection {
    private static final String URL = //localhost:/thread/;
    private HttpConnection httpConnection;
    private String message;
    
    public NetworkConnection(String message) {
        ssage = message;
        connect();
    }
    
    /**
     * Connect to web server
     *
     */
    public void connect(){
        try {
            httpConnection = (HttpConnection) Connectoropen(URL);
            (HttpConnectionPOST);
        } catch (IOException ex) {
            Systemoutprintln(Can not open connection!);
            exprintStackTrace();
        }
    }
    
    
    /**
     * Send message to server
     * @throws javaioIOException 
     */
    public void sendMessage() throws IOException{
        DataOutputStream out = ();
        outwriteUTF(message);
        outclose();
    }
    
    /**
     * Receive message from server
     * @throws javaioIOException 
     * @return 
     */
    public String receiveMessage() throws IOException {
        DataInputStream in = ();
        String message = inreadUTF();
        inclose();
        return message;
    }
    
    /**
     * Close connection
     */
    public void close(){
        if(httpConnection!=null){
            try {
                ();
            } catch (IOException ex) {
                exprintStackTrace();
            }
        }
    }
    
}     構造函數的參數是將要被發送的消息服務器端的代碼在此不再列出詳細請見本文的源代碼
    接著我們寫一個MIDlet調用類中的方法MalConnectionMidlet在commandAction()方法中直接調用NetworkConnection中的方法而沒有重新創建一個線程代碼如下

   以下是引用片段
/*
 * MalConnectionMidletjava
 *
 * Created on  下午:
 */
package njunnection;
import javaioIOException;
import javaxmicroeditionmidlet*;
import javaxmicroeditionlcdui*;
/**
 *
 * @author  Magic
 * @version
 */
public class MalConnectionMidlet extends MIDlet implements CommandListener {
    private Display display;
    private TextBox text;
    private Command showCommand;
    
    public MalConnectionMidlet(){
        display = DisplaygetDisplay(this);
        text = new TextBox(Message請使用問候命令發送消息TextFieldANY);
        showCommand = new Command(問候CommandSCREEN);
        textaddCommand(showCommand);
        textsetCommandListener(this);
    }
    
    public void startApp() {
        displaysetCurrent(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 {
                connectionsendMessage();
                message = connectionreceiveMessage();
                connectionclose();
            } catch (IOException ex) {
                exprintStackTrace();
            }
            textsetString(message);
        }
    }
}     當用戶按下問候命令時就會向服務器發送Hello World的消息然後再得到服務器返回的消息並顯示在TextBox中
    運行程序如圖所示當用戶按下問候命令後程序就僵死了並在Console窗口中得到如下警告

  

  圖

   以下是引用片段
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()中發送接受消息的操作移到此類中完成代碼如下


   以下是引用片段
/*
 * NetworkThreadjava
 *
 * Created on  下午:
 *
 */
package njunnection;
import javaioIOException;
import javaxmicroeditionlcduiTextBox;
/**
 *
 * @author Magic
 */
public class NetworkThread extends Thread {
    private NetworkConnection connection;
    private TextBox text;
    
    public NetworkThread(TextBox text) {
        super();
        thistext = text;
    }
    public void run() {
        String message = null;
            
        connection = new NetworkConnection(Hello World);
        try {
            connectionsendMessage();
            message = connectionreceiveMessage();
            connectionclose();
        } catch (IOException ex) {
            exprintStackTrace();
        }
        textsetString(message);
    }
    
}     同時修改原先的MIDlet得到新的MIDlet

   以下是引用片段
/*
 * ConnectionMidletjava
 *
 * Created on  下午:
 */
package njunnection;
import javaioIOException;
import javaxmicroeditionmidlet*;
import javaxmicroeditionlcdui*;
/**
 *
 * @author  Magic
 * @version
 */
public class ConnectionMidlet extends MIDlet implements CommandListener {
    private Display display;
    private TextBox text;
    private Command showCommand;
    
    public ConnectionMidlet(){
        display = DisplaygetDisplay(this);
        text = new TextBox(Message請使用問候命令發送消息TextFieldANY);
        showCommand = new Command(問候CommandSCREEN);
        textaddCommand(showCommand);
        textsetCommandListener(this);
    }
    
    public void startApp() {
        displaysetCurrent(text);
    }
    
    public void pauseApp() {
    }
    
    public void destroyApp(boolean unconditional) {
    }
    public void commandAction(Command command Displayable displayable) {
        if(command==showCommand){
            /**
             * 創建新的線程完成聯網操作
             */
            (new NetworkThread(text))start();
        }
    }
}     此時在commandAction()中我們創建了新的線程來完成消息的發送和接受運行程序可以成功接受到返回的消息如圖所示

  

  圖

  多線程與拍照

  同樣在移動多媒體API(JSR )的使用過程中也需要應用到多線程利用手機攝像頭拍照的操作也會引起的堵塞因此當在commandAction()中調用拍照操作時若未開辟新線程來處理同樣也會造成程序沒有響應讓我們通過一個例子來觀察一下吧
    這是一個很簡單的攝像頭程序首先程序將會調用攝像頭取景然後當用戶按下拍照命令後就捕捉當前的畫面並顯示給用戶是程序的UML類圖

  

  圖

  MalCameraMidlet和CameraMidlet分別是錯誤和正確的MIDlet它們都創建一個CameraView(用於顯示攝像頭畫面)的對象唯一的不同在於前者創建MalCamera的對象後者創建Camera的對象(MalCamera和Camera都是CameraView的子類)SnapShot則是將攝像頭捕捉到的圖像顯示給用戶的類
    首先我們還是來看一下未使用多線程而造成程序沒有響應的情況如下是MalCameraMidlet類的代碼其中保存著一個指向CameraView的引用並在startApp()中創建了MalCamera對象

   以下是引用片段
/**
 * MalCameraMidletjava
 * 
 */
package njuhysteriathreadcamera;
import javaxmicroeditionlcduiDisplay;
import javaxmicroeditionmidletMIDlet;
/**
 * 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 = DisplaygetDisplay(this);
 }
 protected void startApp() {
  camera = new MalCamera(this);
  displaysetCurrent(camera);
 }
 
       /**
        * Show current camera
        */
 public void showCamera(){
  displaysetCurrent(camera);
 }
 
 protected void pauseApp() {
 }
 protected void destroyApp(boolean arg) {
 }
}     CameraView是一個抽象類是兩個顯示Camera的類的父類它負責顯示Camera並將commandAction()方法留給子類實現代碼如下

   以下是引用片段
/**
 * CameraViewjava
 */
package njuhysteriathreadcamera;
import javaioIOException;
import javaxmicroeditionlcduiCommand;
import javaxmicroeditionlcduiCommandListener;
import javaxmicroeditionlcduiDisplayable;
import javaxmicroeditionlcduiForm;
import javaxmicroeditionlcduiItem;
import jadiaManager;
import jadiaMediaException;
import jadiaPlayer;
import jantrolVideoControl;
import javaxmicroeditionmidletMIDlet;
/**
 * 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(照相);
  thismidlet = midlet;
  
  exitCommand = new Command(退出CommandEXIT);
  captureCommand = new Command(拍照CommandSCREEN);
  
  addCommand(exitCommand);
  addCommand(captureCommand);
  
  setCommandListener(this);
  
  /**
   * Create camera player and control
   */
  try {
   player = ManagercreatePlayer(capture://video);
   playerrealize();
   
   vc = (VideoControl)playergetControl(VideoControl);
   append((Item)vcinitDisplayMode(VideoControlUSE_GUI_PRIMITIVEnull));
   playerstart();
  } catch (IOException e) {
   eprintStackTrace();
  } catch (MediaException e) {
   eprintStackTrace();
  }
 }
 
 public abstract void commandAction(Command cmdDisplayable displayable);
}

  [NextPage]

  MalCamera和Camera都繼承了CameraView類並分別實現了commandAction()方法先來看一下MalCamera


   以下是引用片段
/**
 * MalCamerajava
 */
package njuhysteriathreadcamera;

import javaxmicroeditionlcduiCommand;
import javaxmicroeditionlcduiDisplayable;
import jadiaMediaException;
import javaxmicroeditionmidletMIDlet;
/**
 * 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 {
    playerstop();
   } catch (MediaException e) {
    eprintStackTrace();
   }
   playerclose();
   ((MalCameraMidlet)midlet)destroyApp(false);
   midletnotifyDestroyed();
  }else if(cmd==captureCommand){
   // Do not handle in a new thread
   try {
    byte[] data = vcgetSnapshot(null);
    new SnapShot(midletdata);
   } catch (MediaException e) {
    eprintStackTrace();
   }
  }
 }
}     其中SnapShot是顯示捕捉到的圖像的界面詳細請看文後的源代碼這裡不再贅述
    現在運行MalCameraMidlet按下拍照命令後出現詢問是否記錄圖像的提示但是當你按下Yes後程序就再也沒有響應了如圖所示

  

  圖

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

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

  同樣還是由於沒有創建新的線程進行處理的原因下面我們就把處理的代碼放到新的線程中來完成看看情況如何如下是修改過的MalCamera代碼Camerajava:

   以下是引用片段
/**
 * Camerajava
 */
package njuhysteriathreadcamera;
import javaxmicroeditionlcduiCommand;
import javaxmicroeditionlcduiDisplayable;
import jadiaMediaException;
import javaxmicroeditionmidletMIDlet;
/**
 * 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 {
    playerstop();
   } catch (MediaException e) {
    eprintStackTrace();
   }
   playerclose();
   ((CameraMidlet)midlet)destroyApp(false);
   midletnotifyDestroyed();
  }else if(cmd==captureCommand){
   // Handle in a new thread
   new Thread(){
    public void run(){
     try {
      byte[] data = vcgetSnapshot(null);
      new SnapShot(midletdata);
     } catch (MediaException e) {
      eprintStackTrace();
     }
    }
   }start();
  }
 }
}     同樣我們也需要在MIDlet中創建Camera的對象而不是MalCameraCameraMidlet的代碼如下

   以下是引用片段
/**
 * CameraMidletjava
 */
package njuhysteriathreadcamera;
import javaxmicroeditionlcduiDisplay;
import javaxmicroeditionmidletMIDlet;
/**
 * The correct MIDlet
 * @author Magic
 *
 */
public class CameraMidlet extends MIDlet {
 protected Display display;
 private CameraView camera;
 
 public CameraMidlet() {
  super();
  display = DisplaygetDisplay(this);
 }
 protected void startApp() {
  camera = new Camera(this);
  displaysetCurrent(camera);
 }
       /**
        * Show current camera
        */
 public void showCamera(){
  displaysetCurrent(camera);
 }
 
 protected void pauseApp() {
 }
 protected void destroyApp(boolean arg) {
 }
}     運行CameraMidlet按下拍照命令這次程序沒有堵塞我們可以得到捕捉到的圖片如圖所示

  

  圖

   

  [NextPage]

  Timer與TimerTask

  聯網和拍照這兩種情況都需要程序員創建新的線程來完成任務並且這種做法對於程序員來說是顯式的即通過直接使用Thread類或Runnable接口來直接創建新線程在MIDP的API中同樣提供了隱式的方式來創建新線程以方便程序員的編程這就是TimerTask類它實現了Runnable接口用戶只需創建一個繼承它的類並且實現run()方法以此來創建新線程而無需顯示的繼承Thread或Runnable
    當然TimerTask的優點不僅於此從它的名字來看可以認為它是一個在特定時間執行的任務run()方法中代碼就是這任務那麼怎麼控制其在特定時間執行呢?這就需要Timer這個類的幫助了顧名思義Timer是一個定時器通過調用它的多個schedule()方法中的一個可以控制在特定的時間或每隔一定時間執行TimerTask具體的方法介紹請看JDK文檔
    TimerTask和Timer經常一起使用比如在顯示時間倒計時和顯示歡迎界面時會經常用到下面就通過一個實例來介紹這兩個的用法
    這是一個計時的程序程序從秒開始用戶可以隨時暫停或繼續計時程序是通過Timer和TimerTask來完成的包含三個類ClockMidletClockCanvas和Clock首先來看一下本程序最主要的類ClockCanvas


   以下是引用片段
/**
 * ClockCanvasjava
 */
package njuhysteriathreadclock;
import javautilTimer;
import javaxmicroeditionlcduiCanvas;
import javaxmicroeditionlcduiCommand;
import javaxmicroeditionlcduiCommandListener;
import javaxmicroeditionlcduiDisplayable;
import javaxmicroeditionlcduiFont;
import javaxmicroeditionlcduiGraphics;
/**
 * 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){
  thismidlet = midlet;
  exitCommand = new Command(退出CommandEXIT);
  stopCommand = new Command(停止CommandSCREEN);
  resumeCommand = new Command(繼續CommandSCREEN);
  
  addCommand(exitCommand);
  addCommand(stopCommand);
  setCommandListener(this);
  
  second = ;
  x = getWidth()/  ;
  y = getHeight()/  ;
  
// Create timer and start it
  timer = new Timer();
  timerschedule(new Clock(this));

 }
 
 /**
  * Add one second
  *
  */
 public void addSecond(){
  second++;
 }
 
 protected void paint(Graphics g) {
  gsetColor();
  gfillRect(getWidth()getHeight());
  gsetColor();
  gsetFont(FontgetFont(FontFACE_SYSTEMFontSTYLE_PLAINFontSIZE_LARGE));
  // Draw second
  gdrawString(StringvalueOf(second)xyGraphicsLEFT|GraphicsTOP);

 }
 public void commandAction(Command cmd Displayable displayable) {
  if(cmd==exitCommand){
   timercancel();
   midletdestroyApp(false);
   midletnotifyDestroyed();
  }else if (cmd==stopCommand){
   timercancel();
   removeCommand(stopCommand);
   addCommand(resumeCommand);
  }else if (cmd==resumeCommand){
timer = null;
   timer = new Timer();
   timerschedule(new Clock(this));

   removeCommand(resumeCommand);
   addCommand(stopCommand);
  }
 }
}     ClockCanvas繼承了Canvas用來向用戶顯示當前的秒數注意構造函數中黑體的代碼創建了Timer並且調用schedule()方法來設定運行任務的時間第一個參數是TimerTask的對象這裡是Clock它繼承了TimerTask我們將稍後討論第二個參數是運行的延遲這裡為也就是立刻執行第三個參數是連續運行的時間間隔這裡為毫秒也就是每隔秒鐘更新界面
    注意commandAction()方法在處理停止命令時需要停止計時此處調用timercancle()來取消計時器所以界面將停止更新當按下繼續命令後又需要繼續開始計時所以我們重新創建了Timer因為原來的已經取消了是不可用的了
    接著就來看看Clock是如何來工作的代碼如下

   以下是引用片段
/**
 * Clockjava
 */
package njuhysteriathreadclock;
import javautilTimerTask;
/**
 * Update the time
 * @author Magic
 *
 */
public class Clock extends TimerTask {
 private ClockCanvas canvas;
 
 public Clock(ClockCanvas canvas){
  thiscanvas = canvas;
 }
 
 public void run() {
  canvasaddSecond();
  canvasrepaint();
 }
}

  非常簡單在run()方法中就是調用了ClockCanvas類中addSencond()方法來增加時間同時調用repaint()來更新界面從表面上看幾乎沒有多線程的痕跡其實創建Timer的時候就相當於在後台創建了一個新的線程它控制著TimerTask的執行因此對於秒數的增加是在另一個線程的完成的而主線程只負責更新顯示
   在加上下面的ClockMidlet就可以運行程序了

   以下是引用片段
/**
 * ClockMidletjava
 */
package njuhysteriathreadclock;
import javaxmicroeditionlcduiCanvas;
import javaxmicroeditionlcduiDisplay;
import javaxmicroeditionmidletMIDlet;
/**
 * Clock MIDlet
 * @author Magic
 *
 */
public class ClockMidlet extends MIDlet {
 private Display display;
 private Canvas clockCanvas;
 
 public ClockMidlet() {
  super();
  display = DisplaygetDisplay(this);
 }
 protected void startApp(){
  clockCanvas = new ClockCanvas(this);
  displaysetCurrent(clockCanvas);
 }
 protected void pauseApp() {
 }
 protected void destroyApp(boolean arg) {
 }
}    運行程序將以秒為單位進行計時如圖所示

  

  圖

  總  結

  以上介紹了多線程技術在聯網拍照中的應用以及MIDP自帶的TimerTask和Timer的使用方法在實際編程過程中會遇到更多的使用多線程的情況比如播放背景音樂等但基本方法都是如此這裡不再贅述了
    由於筆者水平有限以上論述只是蜻蜓點水未能深入討論多線程的使用特別是防止死鎖以及wait()/notify()的使用


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27462.html
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.