在Android中,从网络下载图片数据可以使用Java.net包下的URL、URLConnection、InputStream等类。然后使用Android SDK中的BitmapFactory.decodeStream方法将流中的图片数据解析为Bitmap格式的数据。
BitmapFactory类用于创建、解析图像并保存至Bitmap对象中。本章将用到该类的decodeStream方法。
Bitmap decodeStream(InputStream is);
作用:从输入流中解析图像,返回一个Bitmap对象。
参数-is:InputStream对象。
为方便学习、测试与下载相关的网络编程,以下介绍HFS软件,HFS是HTTP File Server(HTTP文档服务器) 的简称。
HFS是专为个人用户所设计的 HTTP 档案系统 - Http File Server,这个软件可以方便地提供档案传输系统,下载后无须安装,只要解压缩后执行 hfs.exe,于「Virtual File System(虚拟档案系统)」窗格下按鼠标右键,即可新增/移除虚拟档案资料夹,或者,直接将欲加入的档案拖曳至此窗口,便可架设完成个人 HTTP 虚拟档案服务器。
以下结合本章网络编程需要使用的服务器端的文件,介绍如何使用HFS。本章需要使用以下资料:
1、baidu.html:该文件显示百度首页。
2、android.html:该文件用于测试与手机浏览器与HML文件中的JavaScript函数实现数据交换。
3、logo_cn.png:Google网站的Logo图标。
4、generals文件夹,该文件夹中存放三国15个著名将领的头像。
5、hfs.exe:HFS软件。
6、hfs.ini:HFS的配置文件。
提示:以上文件在chapters/ch19文件夹中分别以hfs和generals文件夹保存,如图-1和图-2所示:
图-1
图-2
步骤1、双击图-2中的hfs.exe启动该服务器,出现图-3所示的界面:
图-3
步骤2、将图-2中的baidu.htm、android.html和logo_cn.png拖拽至图-3中“显示服务器中所有文件”区域。
步骤3、选中图-1中的generals文件夹,然后拖拽该文件夹至图-3中“显示服务器中所有文件”区域,此时出现图-4提示:
图-4
步骤4、选择“虚拟目录”,出现图-5所示:
图-5
说明:
1、图-5左侧区域(虚拟文件系统)显示已存放在该服务器中的文件和文件夹。
2、单击图-5中标注(1)所指的logo_cn.png文件,在标注(2)所指的地址栏将显示该文件的网址。
提示:
不同电脑的IP地址不同,为编程时方便,请一律用http://10.0.2.2作为服务器的根目录的地址。如图-5中标注(2)的网址在手机编程时请用http://10.0.2.2.long_cn.png访问。
步骤1、创建URL对象,网址是hfs服务器中被下载图片的网址,示例代码如下:
URL url=new URL("http://10.0.2.2/logo_cn.png");
步骤2、打开网络连接,示例代码如下:
URLConnection connection=url.openConnection();
步骤3、获取输入流,示例代码如下:
InputStream is=connection.getInputStream();
步骤4、从输入流中解析图片,示例代码如下:
Bitmap bitmap=BitmapFactory.decodeStream(is);
步骤5、显示图片,示例代码如下:
iv.setImageBitmap(bitmap);
说明:iv被定义为以下形式:
1、ImageView iv=(ImageView)findViewById(R.id.imageView);
2、步骤1中的网址就是图-5中logo_cn.png在手机编程中访问的地址。
从HFS服务器中下载logo_cn.png至手机中并显示如图-6所示的效果:
图-6
关键步骤:
步骤1、创建项目exer19_01,在项目清单文件中申请网络服务权限,如下代码所示:
<uses-permission android:name="android.permission.INTERNET"/>
步骤2、在MainAct.java中输入以下代码:
public class MainAct extends Activity {
//下载图片的按钮
Button btnDownloadPicture;
ImageView iv;//显示图片
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
iv=(ImageView)findViewById(R.id.iv);
btnDownloadPicture=(Button)findViewById(R.id.btnDownloadPicture);
//定义下载图片的按钮单击事件
btnDownloadPicture.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
//创建URL对象,网址是hfs服务器中被下载图片的网址
URL url=new URL("http://10.0.2.2/logo_cn.png");
//打开网络连接
URLConnection connection=url.openConnection();
//获取输入流
InputStream is=connection.getInputStream();
//从输入流中解析图片
Bitmap bitmap=BitmapFactory.decodeStream(is);
iv.setImageBitmap(bitmap);//显示图片
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
下载文本数据与下载图像数据的前三步相同,第四步要将输入流中的字节存放在一个byte类型的数组中,第五步要将该数组以文件原始的编码格式解码存放在一个字符串变量中。
步骤1、创建URL对象,连接的目标地址为http://192.168.170.193/baidu.htm
URL url=new URL("http://10.0.2.2/baidu.htm");
步骤2、打开网络连接
URLConnection connection=url.openConnection();
步骤3、获取输入流
InputStream is=connection.getInputStream();
步骤4、将输入流中的文件内容读入保存在字节数组data中
for(int i=0;(data[i]=(byte)is.read())!=-1;i++){ }
步骤5、将data数组以编码UTF-8格式存放在字符串code
String code=new String(data, "UTF-8");
步骤6、显示结果
tv.setText(code);//显示结果
说明:
1、data数组被定义为如下格式:byte[] data=new data[10000];
2、tv定义为如下格式:TextView tv=(TextView)findViewById(R.id.textView);
3、baidu.htm文件的编码是UTF-8,因此在步骤5中以UFT-8解码。
将图-5中的baidu.htm文件下载至手机中,并显示该文件的HTML源代码,运行效果如图-7所示:
图-7
关键步骤:
步骤1、申请网络权限。
步骤2、在MainAct.java中输入以下代码:
//本类完成从HFS服务器中下载baidu.htm文件,并显示该文件的源代码
public class MainAct extends Activity {
TextView tv;
//定义数组data以字节形式存储读取的服务器端的HTML文本
byte[] data=new byte[10000];
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv=(TextView)findViewById(R.id.tvHtml_code);
Button btnDownloadHtmlCode=(Button)findViewById(R.id.btnDownloadHmlCode);
btnDownloadHtmlCode.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
//创建URL对象,连接的目标地址为http:// 10.0.2.2/baidu.htm
URL url=new URL("http://10.0.2.2/baidu.htm");
//打开网络连接
URLConnection connection=url.openConnection();
//获取输入流
InputStream is=connection.getInputStream();
//将输入流中的文件内容读入保存在字节数组data中
for(int i=0;(data[i]=(byte)is.read())!=-1;i++){
}
//将data数组以编码UTF-8格式存放在字符串code
String code=new String(data, "UTF-8");
tv.setText(code);//显示结果
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
19.2.2.介绍的下载效率不太高,运行以上代码明显有等待的感觉,使用Java的缓冲区技术可以有效地提高下载速度,首先介绍一个类:
该类是字节数组的操作类,用该类定义的字节数组对象可以实现数组之间的高效复制,同时该数组对象可以很方便地转换为字节数组。
1、append(final byte[] b, int off, int len);
作用:将指定字节数组附加到当前数组对象。
参数-b:附加的字节数组。
参数-off:指定该数组复制的起始索引。
参数-len:复制数组元素的个数。
2、toByteArray();
作用:将ByteArrayBuffer对象转换为字节数组。
步骤1、创建URL对象,连接的目标地址为http://10.0.2.2/baidu.htm,示例代码:
URL url=new URL("http://10.0.2.2/baidu.htm");
步骤2、打开连接,示例代码:
URLConnection connection=url.openConnection();
步骤3、获取输入流,示例代码:
InputStream is=connection.getInputStream();
步骤4、创建数组字节缓冲数组,示例代码:
ByteArrayBuffer buffer=new ByteArrayBuffer(3000);
步骤5、创建缓冲区字节数组,示例代码:
byte[] byteBuffer=new byte[1024];
步骤6、循环,每次读取一个缓冲区大小的字节,示例代码:
for(;is.read(byteBuffer)!=-1;){
//复制到数组buffer
buffer.append(byteBuffer,0,byteBuffer.length);
}
步骤7、将字节数组按UTF-8格式转换,结果保存在code中
String code=new String(buffer.toByteArray(), "UTF-8");
步骤8、显示结果
tv.setText(code);
Android提供了在手机中浏览网页的功能,Android系统的手机可以象PC机用浏览器来访问网站,大大方便了手机用户的网上冲浪。
Android移植了WebKit作为手机浏览器的引擎。WebKit是一个开源网页浏览器的引擎,因其开源和高效的网页加载速度被Mac OS、Android、Chrome等浏览器采用。
WebKit由三个模块构成:WebKit、JavaScript和WebCore。本章介绍以下类:
1、WebKit模块中的WebView对象专门用于浏览网页,是一个功能齐全的移动设备浏览器。
2、WebSettings类:管理网页视图的各种设置。
3、WebViewClient类:用于接收来自网页视图的各种消息和请求。
4、WebChromeClient类:用于辅助WebView处理JavaScript的对话框、网站图标、网站Title、加载进度等。
5、OnTouchListener接口:该接口负责处理用户触摸手机中WebView所在区域的事件。
1、setWebViewClient()方法
作用:该方法创建一个客户端浏览器对象,其中WebViewClient是客户端浏览器类,用于接收来自网页视图的消息和请求。
示例代码:webView.setWebViewClient(new WebViewClient());
2、setWebChromeClient()方法
作用:该方法创建一个WebChromeClient对象,用于处理网页视图中的JavaScript的对话框、标题、图标、进度等。
示例代码:webView.setWebChromeClient(new WebChromeClient());
3、getSettings()方法
作用:创建一个WebSettings对象。
示例代码:WebSettings webSettings=webView.getSettings();
4、loadUrl()方法
作用:载入指定网址的网页
示例代码:
1、载入指定地址的网页:webView.loadUrl(“www.baidu.com”);
2、调用JavaScript函数: webView.loadUrl(“javascript:xxx’”+param+”’”);
提示:以上代码的前提是webView加载了含有JavaScript方法的html。
5、addJavascriptInterface(Object obj, String interfaceName);
作用:设置JavaScript接口,允许网页中的JavaScript函数调用Java中的方法。
参数-obj:自定义的类的实例。
参数-interfaceName:设置代表第一个参数所在类的名称,该名称将在JavaScript中被使用。
示例代码:
addJavascriptInterface(new MyClass(), "exer19_04");
说明:MyClass类是自定义类,例如定义为以下形式:
class MyClass{
//runOnJs方法被JavaScript的调用
public void runOnJs(String message){
//创建一个消息对象
Message msg=new Message();
msg.obj=message;
//将该消息发送至主线程,目的是在主线程中显示从JavaScript传递过来的字符串
mHandler.sendMessage(msg);
}
}
在JavaScript中用如下代码调用MyClass.runOnJs()方法:
window.exer19_04.runOnJs(str);
1、setJavaScriptEnabled(Boolean enable)方法
作用:设置是否支持JavaScript脚本。
参数-enable:true表示支持JavaScript。
示例代码:
webSettings.setjavaScriptEnabled(true);
提示:
(1)Android中可以使用JavaScript函数
(2)webSettings是WebSeetings类的对象
1、onProgressChanged()
作用:加载进度条改变时调用本方法。
2、RequestFocus()
作用:设置WebView获取焦点
1、boolean onTouch(View v, MotionEvent event)
作用:当用户触摸网页时,本方法将被调用。
参数-v:网页中被触摸的控件。
参数-event:存放触摸事件的相关信息。
示例1、网页加载时,显示加载进度条,用户触摸网页时,网页获得焦点。
示例代码:
WebView mWebView=(WebView)findViewById(R.id.wv);
mWebView.requestFocus();//设置页面焦点
//加载网页
mWebView.loadUrl("10.0.2.2/android.htm");
//设置WebChromeClient对象
mWebView.setWebChromeClient(new WebChromeClient(){
//重写本方法,实现网页加载时的进度条显示
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
mProgressBar.setProgress(newProgress);//显示网页加载进度
}
});
//设置网页触摸监听事件
mWebView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mWebView.requestFocus();//设置网页获得焦点
return false;
}
});
}
示例2、Java代码调用JavaScript函数
WebView mWebView=(WebView)findViewById(R.id.wv);
mWebView.loadUrl("javascript:get4Android('"+content+"')");
提示:
get4Android是JavaScript函数,该函数带一个字符串参数,通过以上调用可以将Android中的数据传递给网页。
提示:JavaScript调用Android方法在Android2.3.3版本有Bug,请在AndroidSDK2.2版本中运行本项目。
手机在浏览网页时,当网页中图片较多,下载流量将变大。如果用普通下载图片的方式,只有等当前网页中所有图片下载完才能显示网页内容。这将极大地影响用户上网冲浪的体验。以下举例说明:
从HFS服务器上下载generals文件夹中所有的图片,并显示在模拟器的ListView控件中,效果如图-8所示:
图-8
以下是普通方式下载图片的关键代码:
//本类在主线程中完成从服务器下载三国15个将领头像,并在垂直列表中显示这些将军的头像和姓名
public class MainAct extends Activity {
ListView mListView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListView=(ListView)findViewById(R.id.lv);
//设置ListView与自定义的适配器相关联
mListView.setAdapter(new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//创建布局解析器对象
LayoutInflater inflator=LayoutInflater.from(MainAct.this);
//获取布局listView
View layout=inflator.inflate(R.layout.lsitview, null);
//获取该布局中的两个控件
ImageView ivPhoto=
(ImageView)layout.findViewById(R.id.ivPhoto);
TextView tvName=(TextView)layout.findViewById(R.id.tvName);
//显示将军的姓名
tvName.setText(CommonUtils.NAMES[position]);
URL url;
try {
//创建URL对象
url = new URL(CommonUtils.PHOTO_URLS[position]);
//根据url下载指定将军的头像
Bitmap bmp=download(url);
ivPhoto.setImageBitmap(bmp);//显示将军的头像
return layout;//返回该布局对象
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
//返回当前列表项的索引值
@Override
public long getItemId(int position) {
return position;
}
//返回当前列表项
@Override
public Object getItem(int position) {
return null;
}
//返回列表项总数
@Override
public int getCount() {
// TODO Auto-generated method stub
return CommonUtils.NAMES.length;
}
});
}
//自定义方法,每次从服务器上下载一个图片并以Bitmap类型返回该图像
private Bitmap download(URL url){
try {
URLConnection connection=url.openConnection();
InputStream is=connection.getInputStream();
Bitmap bmp=BitmapFactory.decodeStream(is);
return bmp;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
说明:
运行以上代码将会有比较明显地等待感觉,才能看到图-8中的结果。如果图片更多,那么等待的时间会更长。以下用AsyncTask类实现异步加载图片,即在一个线程中下载图片,边下载边显示图片。
Android提供的AsyncTask类,使创建需要与用户界面交互的长时间运行的任务变得更简单。相对来说AsyncTask更轻量级一些,适用于简单的异步处理,不需要借助线程和Handler即可实现。
AsyncTask是抽象类,通过定义子类使用该类提供的功能,如下所示:
class MyAsyncTask extends AsyncTask< Params,Integer, Result>{
@Override
protected Result doInBackground(Params... args) {
//在工作线程中执行耗时操作
return null;
}
//
@Override
protected void onPostExecute(Result result) {
//
}
}
说明:
AsyncTask定义了三种泛型类型,如以上代码中红框所示:Params,Integer和Result。
参数-Params:启动任务执行的输入参数,即doInBackground方法中的形参。
Integer-后台任务执行的百分比。
Result-后台执行任务最终返回的结果。
AsyncTask的使用步骤(每一步都对应一个回调方法,这些方法不由应用程序调用,开发者需要做的就是实现这些方法):
步骤1、 子类化AsyncTask
步骤2、实现AsyncTask中定义的下面一个或几个方法
doInBackground(Params... params),;
作用:该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
参数-params的类型是泛型,该参数就是以上代码中红框中的第一个参数。
protected final void publishProgress(Progress... values);
作用:将doInBackground方法中更新的进度值传递给onProgressUpdate方法。
onProgressUpdate(Integer progress);
作用:在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
参数-progress:通常是一个整形变量。获得计算后的进度值。
onPostExecute(Integer progress), 在doInBackground 执行完成后,onPostExecute 方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程.
步骤3、Task的实例在UI 线程中创建
步骤4、 execute方法在UI 线程中调用
提示:
(1)不要手动调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法
(2) 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第三个为doInBackground返回和onPostExecute传入的参数。
步骤1、创建MyAsyncTask类,该类继承自AsyncTask示例代码:
//创建自定义的异步任务类
class MyAsyncTask extends AsyncTask<URL, Void, Bitmap>{
int mPosition;//当前列表项的索引
ImageView mImageView;//显示当前头像的控件
//构造方法
public MyAsyncTask(int position,ImageView imageView) {
mPosition=position;
mImageView=imageView;
}
//在线程中下载一个头像图片
@Override
protected Bitmap doInBackground(URL... params) {
Bitmap bmp=download(params[0]);//下载一个图片
return bmp;
}
//doInBackground执行结束后,执行本方法,并获得该方法返回的结果
@Override
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);//在控件中显示图片
}
}
说明:
Download()方法的代码参见19.4.1.概述中的download方法。
步骤2、在MainAct.onCreate()中编写显示下载图片的代码,示例代码:
ListView lv=(ListView)findViewById(R.id.lv);
//创建自定义的适配器对象并与垂直列表控件相关联
lv.setAdapter(new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//创建布局解析器对象
LayoutInflater inflator=LayoutInflater.from(MainAct.this);
//创建listview.xml布局对象
View layout=inflator.inflate(R.layout.lsitview, null);
//创建该布局中显示将领姓名的控件,并在当前控件中显示将领姓名
TextView tvName=(TextView)layout.findViewById(R.id.tvName);
tvName.setText(CommonUtils.NAMES[position]);
//获取显示将领头像的控件
ImageView ivPhoto=
(ImageView)layout.findViewById(R.id.ivPhoto);
URL url;
try {
//创建URL对象
url=new URL(CommonUtils.PHOTO_URLS[position]);
/*创建异步任务对象,并执行该异步任务,传入当前列表项的索引值和显示
头像的控件*/
new MyAsyncTask(position, ivPhoto).execute(url);
return layout;//返回显示当前将领的布局对象
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
//返回当前列表项的索引值
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return position;
}
//返回列表项的总长度
@Override
public int getCount() {
return CommonUtils.NAMES.length;
}
});
}
说明:
以上红框中的代码调用了MyAsynTask创建对象并执行了该对象的execute()方法,开始在线程中执行异步加载图片的动作。
提示:
ListView控件本身支持延迟加载,即:只有出现在当前屏幕的图片,才为该图片创建列表对象并下载该图片。
改进以上程序,已被加载的图片不再重新下载,这样将减少流量,提高浏览速度。
实现思路:
用一个HashMap对象保存每次下载的图片,该对象的键是列表项的索引,值是图片对象。
在getView()方法中每次判断该列表项若是空,则下载图片,若不为空,表明该列表项已创建,则从集合中获取已下载的图片并显示。
关键代码如下:
public class MainAct extends Activity {
// cacheMap集合:保存已下载的图片
private HashMap<Integer, Bitmap> cacheMap = new HashMap<Integer, Bitmap>();
// 异步加载,URL:网址,Void:下载进度,这里设置为不返回进度值,Bitmap:返回的结果
class MyAsyncTask extends AsyncTask<URL, Void, Bitmap> {
private ImageView mImageView;
private int mPosition;
// 创建类的构造方法,解决下载线程与存放图片不同步的问题
public MyAsyncTask(int position, ImageView imageView) {
this.mPosition = position;
this.mImageView = imageView;
}
// 执行后台下载
@Override
protected Bitmap doInBackground(URL... params) {
// 执行下载图片的操作
Bitmap bitmap = downloadImg(params[0]);
// 将下载的图片存放在集合中
cacheMap.put(mPosition, bitmap);
// 返回图片对象
return bitmap;
}
// 本方法在doInBackground方法执行完毕后执行,并获得了该方法返回的数据
@Override
protected void onPostExecute(Bitmap result) {
// 显示图片
mImageView.setImageBitmap(result);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView listView = (ListView) findViewById(R.id.listView1);
listView.setAdapter(new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 当前列表项的布局对象
View layout;
if (null == convertView) { //若当前列表项尚未创建
// 创建布局解析器对象
LayoutInflater inflater = LayoutInflater
.from(MainAct.this);
// 获取lv_item.xml的实例
layout = inflater.inflate(R.layout.lv_item, null);
} else { //获取该列表项对象
layout = convertView;
}
// 获得布局中的两个控件
ImageView imageView = (ImageView) layout
.findViewById(R.id.imageView);
TextView textView = (TextView) layout
.findViewById(R.id.textView);
//显示将领姓名
textView.setText(CommonUtils.NAMES[position]);
//判断集合中是否已有该将领的头像图片(是否已下载过)
if (cacheMap.containsKey(position)) {
//从集合中取出该将领头像并显示在imageView中
Bitmap bitmap = cacheMap.get(position);
imageView.setImageBitmap(bitmap);
} else { //否则,执行异步任务线程,下载该将领头像图片
Log.d("listview", "no cache: " + position);
try {
// 创建异步线程对象,确保每个下载的图片都保存在对应的集合元素中
new MyAsyncTask(position, imageView).execute(
new URL(CommonUtils.PHOTO_URLS[position]));
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
return layout;//返回布局对象
}
//返回当前列表项索引
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return position;
}
//返回列表项总数
@Override
public int getCount() {
return CommonUtils.PHOTO_URLS.length;
}
});
}
/**
* 下载指定URL地址的图片 如果url者错误,返回空
* @param strUrl
* @return
*/
protected Bitmap downloadImg(URL url) {
try {
URLConnection openConnection = url.openConnection();
InputStream is = openConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(is);
return bitmap;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}