这篇文章将教你学习在MIDlet中怎样使用线程访问网络链接。
没有了多线程,MIDlet在等待网络响应时会有一个网络阻塞。
在现实中,用户希望即使是有个网络链接在被处理,在此过程中程序也能继续运行。
第一阶段 开始之前
我们用单线程MIDlet访问网络,从它出现的问题上来开始这个话题。这样,你会学习到关于线程你需要了解什么,和使用线程的两种方式。
在最后一个阶段,你将建立一个多线程的MIDlet。这个程序将为你从网络上下载一张图片。一旦链接被初始,进行着下载,用户会返回到程序主界面。即使是下载在进行,用户也可以操作界面,包括键入一个新的URL地址。下载完毕后,MIDlet会启动一个提示对话框,提示用户下载的结果,是完成还是失败,然后再显示下载的图片。
通过这篇文章,你应该对多线程重要性有了一个深刻的理解并有坚实的基础写出自己的多线程MIDlet。
准备资料:
你需要两个软件工具来完成该文章的实现:
JDK JDK提供了JAVA的源代码编译器和生成JAR包的工具。如果使用WT2.2你需要下载JDK1.4或最新的版本。
WTK WTK是一个J2me的集成开发环境。WTK下载包包含了一个IDE,当然也有创建MIDlet的库。
安装软件
这里不在赘述。
第二阶段 单线程MIDlet
概述
从手边的问题开始,开起来不错,也就是说,需要知道为什么我们在访问网络的时候需要采用多线程。接下来你就会看到多线程的重要性。
这个MIDlet显示了一个储存在远方服务器的报价。这里有九个报价可以获得。用户输入一到九的数字去检索和显示其中一个报价。MIDlet程序的目的就是来显示在获得网络请求时是怎么阻塞的。
让我们来开始创建一个project。你将在程序运行时看到弹出屏幕显示问题出在哪里。
创建MIDlet
文章中所有的MIDlet的建立,请遵循下面的步骤来使用wtk:
1.创建工程
2.编写代码
3.编译代码
4.运行MIDlet
下面我们马上来创建一个project。
创建工程
1. 点击New Project。
2. 键入工程名和MIDlet名
3. 点击Create Project。
编写代码
把下面的代码复制粘贴到文本编辑器中。
GetQuote.java
/*--------------------------------------------------
* GetQuote.java
*-------------------------------------------------*/
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
public class GetQuote extends MIDlet implements CommandListener
{
private Display display; // Reference to Display object
private Form fmMain; // The main form
private Command cmRetrieve; // Command to get quote
private Command cmExit; // Command to exit MIDlet
private TextField tfQuote; // Phone number
private String url = "http://www.corej2me.com/ibm/quotes2.php";
/*--------------------------------------------------
* Constructor
*-------------------------------------------------*/
public GetQuote()
{
display = Display.getDisplay(this);
// Create commands
cmRetrieve = new Command("Retrieve", Command.SCREEN, 1);
cmExit = new Command("Exit", Command.EXIT, 1);
// Textfield to prompt for quote number
tfQuote = new TextField("Get quote between 1 and 9:", "", 1,
TextField.NUMERIC);
// Create Form, add Commands & textfield, listen for events
fmMain = new Form("Quote MIDlet");
fmMain.addCommand(cmExit);
fmMain.addCommand(cmRetrieve);
fmMain.append(tfQuote);
fmMain.setCommandListener(this);
}
// Called by application manager to start the MIDlet.
public void startApp()
{
display.setCurrent(fmMain);
}
public void pauseApp()
{ }
public void destroyApp(boolean unconditional)
{ }
public void commandAction(Command c, Displayable s)
{
if (c == cmRetrieve)
{
try
{
// Connect to the server, passing in request quote
String str = connect(tfQuote.getString());
fmMain.append("Quote: " + str);
}
catch (Exception e)
{
System.out.println("Unable to get quote " + e.toString());
}
}
else if (c == cmExit)
{
destroyApp(false);
notifyDestroyed();
}
}
/*--------------------------------------------------
* Connect to the remote server, run the php script
* and return the request quote.
*-------------------------------------------------*/
private String connect(String number) throws IOException
{
InputStream iStrm = null;
ByteArrayOutputStream bStrm = null;
HttpConnection http = null;
String quote_string = null;
try
{
// Create the connection
http = (HttpConnection) Connector.open(url + "?number=" + number);
System.out.println(url + "?number=" + number);
//----------------
// Client Request
//----------------
// 1) Send request method
http.setRequestMethod(HttpConnection.GET);
// If you experience connection/IO problems, try
// removing the comment from the following line
//http.setRequestProperty("Connection", "close");
// 2) Send header information
// 3) Send body/data - No data for this request
//----------------
// Server Response
//----------------
// 1) Get status Line
if (http.getResponseCode() == HttpConnection.HTTP_OK)
{
// 2) Get header information
// 3) Get data from server
iStrm = http.openInputStream();
int length = (int) http.getLength();
int ch;
bStrm = new ByteArrayOutputStream();
while ((ch = iStrm.read()) != -1)
{
// Ignore any carriage returns/linefeeds
if (ch != 10 && ch != 13)
bStrm.write(ch);
}
quote_string = new String(bStrm.toByteArray());
}
}
finally
{
// Clean up
if (iStrm != null)
iStrm.close();
if (bStrm != null)
bStrm.close();
if (http != null)
http.close();
}
return quote_string;
}
}
保存代码
在你创建工程的时候,WTK就给你提供了一个工程路径。你只需要把你的代码拷贝到目录下。
下面的阶段里我们将详细复习代码的细节部分。
编写PHP
现在,写一个PHP来处理用户输入的数字,返回一个适当的报价信息。把下面的代码粘贴到文本编辑器。
<?php
$quotes[] = "I think there is a world market for maybe five computers.
Thomas Watson.";
$quotes[] = "The answer to life's problems aren't at the bottom of a
bottle, they're on TV! Homer Simpson.";
$quotes[] = "I love California , I practically grew up in Phoenix .
Dan Quayle.";
$quotes[] = "640K ought to be enough for anybody. Bill Gates.";
$quotes[] = "Some people are afraid of heights. Not me, I'm afraid
of widths. Steven Wright";
$quotes[] = "Start every day off with a smile and get it over
with. W. C. Fields ";
$quotes[] = "I am not a vegetarian because I love animals;
I am a vegetarian because I hate plants. A. Whitney Brown";
$quotes[] = "A conclusion is simply the place where someone got
tired of thinking. Arthur Block";
$quotes[] = "A rich man's joke is always funny. Proverb.";
//-------------------------------------------------------
// It all starts here.
// Get requested quote number parameter passed in the url
//-------------------------------------------------------
if (isset($_GET))
{
$quote_num = $_GET["number"];
}
// Create a delay to make the problem of no-threads more obvious
sleep(5);
// User entered a quote number
if ($quote_num > 0)
{
// Decrement the quote requested because our array is zero based
echo $quotes[$quote_num - 1];
}
else // User did NOT enter a quote number, return a random quote
{
// Initialize random number generator
srand((double)microtime() * 1000000);
// Get a random value that is between 0 and the length of our array
(minus 1) rand_num = rand(0, (count($quotes) - 1));
// Echo the quote from the array
echo $quotes[$rand_num];
}
?>
保存PHP代码
把这个PHP保存为quotes2.php,放置在web服务器根目录下。
关于PHP代码的解释我们会在下面安排。
编译代码
点击Build去编译,并把MIDlet打包。
点击RUN来执行
执行工程
开始启动MIDlet
点击Launch
在程序执行过程中,我将显示问题出现在哪里。下面是运行的步骤演示。
当用户没有输入任何东西就提交的时候,PHP程序会返回一个随机的信息,下面的图片就是在没有输入的情况下出现的结果:
代码回顾:commandAction()
让我们来简短地回顾一下事件是怎么被处理和链接PHP服务器的细节部分。下面就是处理命令的程序代码。在用户传入自己想要的报价信息的时候,connect()方法被调用了。
public void commandAction(Command c, Displayable s)
{
if (c == cmRetrieve)
{
try
{
// Connect to the server, passing in request quote
String str = connect(tfQuote.getString());
fmMain.append("Quote: " + str);
}
catch (Exception e)
{
System.out.println("Unable to get quote " + e.toString());
}
}
else if (c == cmExit)
{
destroyApp(false);
notifyDestroyed();
}
}
在connect()方法的核心就是链接到服务器并处理服务器反馈的信息。上面的代码中,当接受到服务器的响应后,相应的报价信息已经被更新了。
代码回顾:connect()
如果你做过很多J2ME链接网络的程序,很多代码看起来都很相似。注意我是怎么样把用户输入的请求编辑到URL上的。例如,如果用户输入了5,URL就会变成:
http://www.corej2me.com/ibm/quotes2.php?number=5。用这个URL,我简单地打开链接,指定提交方式,等待反馈结果。
private String connect(String number) throws IOException
{
InputStream iStrm = null;
ByteArrayOutputStream bStrm = null;
HttpConnection http = null;
String quote_string = null;
try
{
// Create the connection
http = (HttpConnection) Connector.open(url + "?number=" + number);
System.out.println(url + "?number=" + number);
//----------------
// Client Request
//----------------
// 1) Send request method
http.setRequestMethod(HttpConnection.GET);
// If you experience connection/IO problems, try
// removing the comment from the following line
//http.setRequestProperty("Connection", "close");
// 2) Send header information
// 3) Send body/data - No data for this request
//----------------
// Server Response
//----------------
// 1) Get status Line
if (http.getResponseCode() == HttpConnection.HTTP_OK)
{
// 2) Get header information
// 3) Get data from server
iStrm = http.openInputStream();
int length = (int) http.getLength();
int ch;
bStrm = new ByteArrayOutputStream();
while ((ch = iStrm.read()) != -1)
{
// Ignore any carriage returns/linefeeds
if (ch != 10 && ch != 13)
bStrm.write(ch);
}
quote_string = new String(bStrm.toByteArray());
}
}
finally
{
// Clean up
if (iStrm != null)
iStrm.close();
if (bStrm != null)
bStrm.close();
if (http != null)
http.close();
}
return quote_string;
}
一旦服务器响应,我就检查链接请求是否正确。如果正确,我就把服务器响应读出来并存储到变量quote_string。在connect()方法的末尾,我把这个变量的值返回。
这就是MIDlet程序的执行过程,你想要得到报价信息,把请求发送给服务器,然后读取显示服务器的响应结果。现在只剩下PHP代码是怎么处理请求报价还没有说了。下面我们就来看看PHP代码:
代码回顾: php
为了生成报价信息,我简单地创建了一个数组并把每一个赋值为一个报价信息。一个更健壮的方式是从文件中读取报价信息。即使不太难,我想我们还是把焦点放在如何完成手边的任务--------怎么样把MIDlet中参数传到服务器并返回和参数对应的响应信息。
前面几行是用来写九条报价信息,他们分别储存在数组里面。检查时候用参数从MIDlet中传过来,如果有,获得这个值并存储在quote_num中。接下来,你就看到一个调用的sleep()方法,休眠5秒钟。你想要添加一个延迟来保证MIDlet有个明显的停顿在等待PHP完成。目的是在显示MIDlet如何停止,如果没有这个停顿,快速的网络和很短的PHP程序会执行的非常快而不利用显示。
<?php
$quotes[] = "I think there is a world market for maybe five computers.
Thomas Watson.";
$quotes[] = "The answer to life's problems aren't at the bottom of a
bottle, they're on TV! Homer Simpson.";
$quotes[] = "I love California , I practically grew up in Phoenix .
Dan Quayle.";
$quotes[] = "640K ought to be enough for anybody. Bill Gates.";
$quotes[] = "Some people are afraid of heights. Not me, I'm afraid of
widths. Steven Wright";
$quotes[] = "Start every day off with a smile and get it over with.
W. C. Fields ";
$quotes[] = "I am not a vegetarian because I love animals; I am a
vegetarian because I hate plants. A. Whitney Brown";
$quotes[] = "A conclusion is simply the place where someone got tired
of thinking. Arthur Block";
$quotes[] = "A rich man's joke is always funny. Proverb.";
//-------------------------------------------------------
// It all starts here.
// Get requested quote number parameter passed in the url
//-------------------------------------------------------
if (isset($_GET))
{
$quote_num = $_GET["number"];
}
// Create a delay to make the problem of no-threads more obvious
sleep(5);
// User entered a quote number
if ($quote_num > 0)
{
// Decrement the quote requested because our array is zero based
echo $quotes[$quote_num - 1];
}
else // User did NOT enter a quote number, return a random quote
{
// Initialize random number generator
srand((double)microtime() * 1000000);
// Get a random value that is between 0 and the length of our array
(minus 1) $rand_num = rand(0, (count($quotes) - 1));
// Echo the quote from the array
echo $quotes[$rand_num];
}
?>
单线程MIDlet总结
当用J2ME写这个程序时,你会发现运行在单线程下访问网络的问题。MIDlet阻塞的原因是程序在系统的线程中运行。如果程序执行过程中等待网络链接的结束,MIDlet也会等待。
任何情况下,这种代码是不被接受的。解决办法就是用一个新的线程来执行网络链接处理。下面我们就会讲述。
第三阶段: 多线程
概述
多线程允许一个程序就多个片段执行。一个多线程程序的例子就是我们都很熟悉的浏览器。当一个网页在下载的时候,你可以移动浏览器,打开菜单项,键入新的网络地址,等等。在一个单线程程序中,这样的操作是不可能的。
回到我们上一个例子。该程序中必须一个操作:访问网络任务的线程,其他的都可以不用改变。
线程创建后,调用start()来启动。线程的运行在run()时开始。你可以暂时调用sleep()停止线程。不像j2se可以调用别的方法来停止线程。在下面开发多线程MIDlet的过程中,你将会看到我们如何克服这个问题。
多线程技术
在J2SE中,有两种方式来实现多线程。一种是继承自Thread,另一种是实现Runnable接口。我们将会浏览每一种选择,你会发现不同情况下各自的差异和优劣。
直接从继承Thread开始是个不错的开头。让我们看看这是怎么做的。
继承Thread
如下所示:
class NetworkThread extends Thread
{
...
public void run()
{
// Thread code goes here
...
}
...
}
创建一个线程并启动它一气呵成。
NetworkThread thread1 = new NetworkThread();
thread1.start();
...
记住,调用start()的结果是run()方法被调用。
实现Runnable接口
如下显示:
public class AppletTest extends Applet implements Runnable
{
...
public void run()
{
// Thread code goes here
...
}
...
}
想要启动AppletTest线程,你需要像下面这么做.
AppletTest someclass = new AppletTest();
...
Thread thread2 = new Thread(AppletTest));
thread2.start();
...
这和前面的例子有点像,你必要要做一些调整,因为你没有直接从Thread中继承。
对比两个方式
我们到底应该怎么样选择使用实现线程的方法呢?
第二个例子就说明了一切:
public class AppletTest extends Applet implements Runnable
{
...
public void run()
{
// Thread code goes here
...
}
...
}
AppletTest继承自Applet,在java中你只能继承一个类,也就是说一个子类只能有一个直接父类。为了实现多个继承,就需要实现接口。
J2SE&J2ME线程的区别
J2ME中的很多东西都是J2SE的缩版,是为了适应移动设备。说到线程,我需要指出几个不同点。
没有线程组 线程是基于object运行的,在J2ME中你不能通过一个方法启动或停止一群线程。
没有守护线程 守护线程是需要要执行的给其他线程提供帮助或服务的。例如,在HotJava浏览器中就有一个守护线程为其他对象或是线程在它们需要的时候读图片。
没有stop()方法 J2ME中的线程终止在run()方法执行完毕。我将会出示一个例子来说明在多线程网络MIDlet中它是这么完成的。
第四阶段 多线程MIDlet
概述
不要再啰嗦了,我们现在就开始写我在文章开头提及的MIDlet-----一个多线程的J2ME程序。这个程序从网络上下载图片,并用TtxtBox组件提示用户。一旦用户开始下载你就会从textBox上获得URL,在弹出的Alert上有一条信息提示下载开始,最后,启动线程进行下载。
下载结束时,MIDlet会用一个新的Alert提示用户下载成功或失败的信息,最后图片会显示出来。
下面是可获得的图片的地址和显示:
http://www.corej2me.com/ibm/lighthouse.png
http://www.corej2me.com/ibm/car.png
http://www.corej2me.com/ibm/pond.png
MIDlet的第一个任务是提示用户输入一个图片的URL。在处理事件的时候我们看到了两个按钮:exit用来退出MIDlet,view用来下载图片。
url textbox
用户选择下来后,弹出一个Alert提示下载开始。
用户返回到主界面。下载工作用一个单独的线程来做,主页面是可操作的,允许你改变TEXTBOX里面的内容指向一个新的URL。
可以重复上面的步骤下载新的图片。
当第一个图片下载完毕,就会提示用户,显示图片。
点击Done
编写代码
前面的步骤都演示过,我们就不再赘述,请复制下面的代码:
/*--------------------------------------------------
* ViewImage.java
**
Download and view png files. Downloading is
* done in the background with a separate thread
*-------------------------------------------------*/
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
/*--------------------------------------------------
* Constructor
*-------------------------------------------------*/
public class ViewImage extends MIDlet implements CommandListener
{
private Display display; // Main display object
private TextBox tbMain; // Textbox to prompt for URL
private Alert alStatus; // Dialog box for download status
private Form fmViewImg; // Form to display images
private Command cmExit; // Command to exit
private Command cmView; // Command to initiate image download
private Command cmBack; // Return to main screen
private String imageName = null; // Name of image to download
Image im = null; // Downloaded image
private static final int ALERT_DISPLAY_TIME = 3000;
public ViewImage()
{
display = Display.getDisplay(this);
// Create the Main textbox with a maximum of 75 characters
tbMain = new TextBox("Enter url",
"http://www.corej2me.com/ibm/lighthouse.png", 75,
TextField.ANY);
// Create commands and add to textbox
cmExit = new Command("Exit", Command.EXIT, 1);
cmView = new Command("View", Command.SCREEN, 2);
tbMain.addCommand(cmExit);
tbMain.addCommand(cmView );
// Set up a listener for textbox
tbMain.setCommandListener(this);
// Create the form that will hold the png image
fmViewImg = new Form("");
// Create commands and add to form
cmBack = new Command("Back", Command.BACK, 1);
fmViewImg.addCommand(cmBack);
// Set up a listener for form
fmViewImg.setCommandListener(this);
}
public void startApp()
{
// Display the textbox that prompts for URL
display.setCurrent(tbMain);
}
public void pauseApp()
{ }
public void destroyApp(boolean unconditional)
{ }
/*--------------------------------------------------
* Process events
*-------------------------------------------------*/
public void commandAction(Command c, Displayable s)
{
// If the Command button pressed was "Exit"
if (c == cmExit)
{
destroyApp(false);
notifyDestroyed();
}
else if (c == cmView)
{
// Save the name of the image to download so we can
// display it in the alert (dialog box).
// This will be the entry after the last '/' in the url
imageName =
tbMain.getString().substring(tbMain.getString().lastIndexOf('/')
+ 1);
// Show alert indicating we are starting a download.
// This alert is NOT modal, it appears for
// approximately 3 seconds (see ALERT_DISPLAY_TIME)
showAlert("Downloading - " + imageName, false, tbMain);
// Create an instance of the class that will
// download the file in a separate thread
Download dl = new Download(tbMain.getString(), this, imageName);
// Start the thread/download
dl.start();
}
else if (c == cmBack)
{
display.setCurrent(tbMain);
}
}
/*--------------------------------------------------
* Called by the thread after attempting to download
* an image. If the parameter is 'true' the download
* was successful, and the image is shown on a form.
* If parameter is 'false' the download failed, and
* the user is returned to the textbox.
**
In either case, show an alert indicating the
* the result of the download.
*-------------------------------------------------*/
public void showImage(boolean flag, String imageName)
{
// Download failed
if (flag == false)
{
// Alert followed by the main textbox
showAlert("Download Failure", true, tbMain);
}
else // Successful download...
{
ImageItem ii = new ImageItem(null, im, ImageItem.LAYOUT_CENTER,
null);
// If there is already an image, set (replace) it
if (fmViewImg.size() != 0)
fmViewImg.set(0, ii);
else // Append the image to the empty form
fmViewImg.append(ii);
// Alert followed by the form holding the image
showAlert("Download Successful - " + imageName, true, fmViewImg);
}
}
/*--------------------------------------------------
* Show an alert with the parameters determining
* the type (modal or not) and the displayable to
* show after the alert is dismissed
*-------------------------------------------------*/
public void showAlert(String msg, boolean modal, Displayable
displayable)
{
// Create alert, add text, associate a sound
alStatus = new Alert("Status", msg, null, AlertType.INFO);
// Set the alert type
if (modal)
alStatus.setTimeout(Alert.FOREVER);
else
alStatus.setTimeout(ALERT_DISPLAY_TIME);
// Show the alert, followed by the displayable
display.setCurrent(alStatus, displayable);
}
}
/*--------------------------------------------------
* Class - Download
**
Download an image file in a separate thread
*-------------------------------------------------*/
class Download implements Runnable
{
private String url;
private ViewImage MIDlet;
private String imageName = null;
private boolean downloadSuccess = false;
public Download(String url, ViewImage MIDlet, String imageName)
{
this.url = url;
this.MIDlet = MIDlet;
this.imageName = imageName;
}
/*--------------------------------------------------
* Download the image
*-------------------------------------------------*/
public void run()
{
try
{
getImage(url);
}
catch (Exception e)
{
System.err.println("Msg: " + e.toString());
}
}
/*--------------------------------------------------
* Create and start the new thread
*-------------------------------------------------*/
public void start()
{
Thread thread = new Thread(this);
try
{
thread.start();
}
catch (Exception e)
{}
}
/*--------------------------------------------------
* Open connection and download png into a byte array.
*-------------------------------------------------*/
private void getImage(String url) throws IOException
{
ContentConnection connection = (ContentConnection)
Connector.open(url);
DataInputStream iStrm = connection.openDataInputStream();
ByteArrayOutputStream bStrm = null;
Image im = null;
try
{
// ContentConnection includes a length method
byte imageData[];
int length = (int) connection.getLength();
if (length != -1)
{
imageData = new byte[length];
// Read the png into an array
iStrm.readFully(imageData);
}
else // Length not available...
{
bStrm = new ByteArrayOutputStream();
int ch;
while ((ch = iStrm.read()) != -1)
bStrm.write(ch);
imageData = bStrm.toByteArray();
}
// Create the image from the byte array
im = Image.createImage(imageData, 0, imageData.length);
}
finally
{
// Clean up
if (connection != null)
connection.close();
if (iStrm != null)
iStrm.close();
if (bStrm != null)
bStrm.close();
}
// Return to the caller the status of the download
if (im == null)
MIDlet.showImage(false, null);
else
{
MIDlet.im = im;
MIDlet.showImage(true, imageName);
}
}
}
代码的分析我就不再赘述了。