1、关于网络图片的查看
注意在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差。
//imageView 显示图片的设置:
iv.setImageResource(resId); 通过资源id,进行设置显示图片
iv.setImageBitmap(bim); //通过位图显示图片, Bitmap 由位图工厂工厂创建 decodeStream(inputstream)获取。
2)任何耗时操作都要写在主线程中。 android.os.NetWorkOnMainThreadException 网络工作在主线程上异常。
就是将服务器端的图片下载到客户端显示出来
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
><Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载图片"
android:onClick="click"
android:layout_centerHorizontal="true"
/>
<ImageView //用来显示图片
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
要得到图片首先要知道图片的网址,path然后封装到url对象中。
获得URlConnection 由url.openConnetction()得到,转成HttpURLConnection
此时并未开始连接,首先对连接对象初始化,设置连接对象的请求方法(注意这里GET区分大小写必须大写否则运行时异常)协议异常 以下代码在4.0以下版本可以正常使用 4.0以上会报运行在主线程上异常。android.os.NetworkOnMainThreadException
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v) {
String path = "http://img05.3dmgame.com/uploads/allimg/150407/265_150407215242_1_lit.png";
try {
// 将网址封装成url对象。
URL url = new URL(path);
// 获取服务器和客户端的连接对象 此时还没有建立连接
HttpURLConnection coon = (HttpURLConnection) url.openConnection();
// 对连接对象进行初始化 设置请求方式
coon.setRequestMethod("GET");
// 设置连接超时时间
coon.setConnectTimeout(5000);
// 设置读取超时时间
coon.setReadTimeout(5000);
// 发送请求,与服务器建立连接。
coon.connect();
// 得到的响应码如果等于200说明请求成功。
if (coon.getResponseCode() == 200) {
// 获取服务器响应头中的流 流里面的数据就是客户端请求的数据
InputStream is = coon.getInputStream();
// 读取流中的数据,并创建位图对象 这时图片就在位图中 这里用到位图工厂 解出流中的图片数据。
Bitmap bm = BitmapFactory.decodeStream(is);
ImageView iv = (ImageView) findViewById(R.id.iv);
// 把位图对象显示在ImageView中。
iv.setImageBitmap(bm);
} else {
Toast.makeText(MainActivity.this, "请求失败", 0).show();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
上面的程序跑在了主线程上。会报异常。
2、主线程阻塞和消息队列
在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差
* 主线程阻塞时间过长,系统会抛出ANR异常
* ANR:Application Not Response;应用无响应
创建一个子线程 从写run方法:
Thread t = new Thread(){
@Override
public void run(){
super.run();
从写父类的run方法。
}
};
t.start;
另外需要注意的是主线程又称为UI线程。因为只有主线程才能刷新UI。
刷新ui的代码只能运行在主线程,运行在子线程是没有任何效果的
* 如果需要在子线程中刷新ui,就要使用消息队列机制。
* 什么是消息队列机制呢
消息队列:looper消息轮巡器 Message Queue 消息队列
Looper一旦发现Message Queue中有消息,就会把消息取出,然后把消息扔给Handler对象,Handler会调用自己的handleMessage方法来处理这条消息
handleMessage方法运行在主线程。
在子线程中发送消息给主线程:
1、现在主线程中创建Handler 对象 复写handleMessage
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) { //复写handlemessage 方法 这里的msg就是子线程发送过来的。
ImageView iv= (ImageView) findViewById(R.id.iv); //只有主线程中才能刷新ui 将视图显示定义在handlemessage方法中。
iv.setImageBitmap((Bitmap) msg.obj); //obj 强转成Bitmap 位图对象。
}
};
Message msg= new Message(); //实际开发当中我们会用Message msg = handler.obtainMessage();获得Message对象节约内存。
//消息对象可以携带数据。
msg.obj = bim;
//把消息发送到主线程的消息队列。
handler.sendMessage(msg);
消息队列机制
* 主线程创建时,系统会同时创建消息队列对象(MessageQueue)和消息轮询器对象(Looper)
* 轮询器的作用,就是不停的检测消息队列中是否有消息(Message)
* 消息队列一旦有消息,轮询器会把消息对象传给消息处理器(Handler),处理器会调用handleMessage方法来处理这条消息,handleMessage方法运行在主线程中,所以可以刷新ui
* 总结:只要消息队列有消息,handleMessage方法就会调用
* 子线程如果需要刷新ui,只需要往消息队列中发一条消息,触发handleMessage方法即可
* 子线程使用处理器对象的sendMessage方法发送消息
注意处理消息时,有成功的消息,也有失败的也需要handlemessage处理怎么区别呢?
message对象中有一个message.what (int类型的值) 就用这个赋值就行区分。
Message msg = handler.obtainMessage();
// 消息对象可以携带数据。
msg.obj = bim;
msg.what = 1;
// 把消息发送到主线程的消息队列。
handler.sendMessage(msg);
----------
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1: //根据msg.what的值进行判断 哪个消息时成功处理的 哪个是处理失败的。
ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setImageBitmap((Bitmap) msg.obj); //负责将图片信息显示在屏幕上,网络图片需要开启数据连接或WiFi
break;
default:
Toast.makeText(MainActivity.this, "请求失败了哦亲", 0).show();
break;
}
}
};
获取一个字符串网址文件名 http://p2.so.qhimg.com/sdr/575_768_/t01cfdbb0c8a5a482d7.jpg 图片名称的方法:
以最后一个反斜杠作为切割符 获取其索引值。
public String getFileName(String path){
int index= path.lastIndexOf("/");
return path.subString(index+1);(从哪个位置开始截取获得)
}
将文件写入缓存文件的代码单元:
InputStream is = coon.getInputStream();
File file = new File(getCacheDir(), getFileName(path));// 放到缓存文件下。 第二个参数是缓存文件的名字
//文件输出流将数据写到文件中。
FileOutputStream fos = new FileOutputStream(file);
byte[] b = new byte[1024]; // 每次读取1k
int len=0;
while((len=is.read(b))!=-1){ //注意必要忘记传入字符数组。b
fos.write(b, 0, len);
}
fos.close(); //这样就将输入流中的数据写入到了缓存文件中。
网络请求图片带缓存版的最终版:
package com.zh.acheshowdata;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import com.zh.acheshowdata.R;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
import android.widget.Toast;
public class MainActivity extends Activity {
static ImageView iv;
static MainActivity ma;
static Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
iv.setImageBitmap((Bitmap) msg.obj);
break;
default:
Toast.makeText(ma, "请求失败了哦亲", 0).show();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv);
ma = this;
final String path = "http://p2.so.qhimg.com/sdr/575_768_/t01cfdbb0c8a5a482d7.jpg";
final File file = new File(getCacheDir(), getFileName(path));// 放到缓存文件下。
if (file.exists()) {
// 如果缓存存在的话
Bitmap bim = BitmapFactory.decodeFile(file.getAbsolutePath());
iv.setImageBitmap(bim);
} else {
Thread t = new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
URL url = new URL(path);
// 由url对象获得打开 连接对象 httpurlconnection
HttpURLConnection coon = (HttpURLConnection) url.openConnection();
// 设置请求的方法 连接超时时间,和读取超时间
coon.setRequestMethod("GET");
coon.setConnectTimeout(5000);
coon.setReadTimeout(5000);
// 判断得到的状态码 此时已经建立连接
if (coon.getResponseCode() == 200) {
InputStream is = coon.getInputStream();
// 文件输出流将数据写到文件中。
FileOutputStream fos = new FileOutputStream(file);
byte[] b = new byte[1024]; // 每次读取1k
int len = 0;
while ((len = is.read(b)) != -1) {
fos.write(b, 0, len);
}
fos.close();
Bitmap bim = BitmapFactory.decodeFile(file.getAbsolutePath());
Message msg = handler.obtainMessage();
// 消息对象可以携带数据。
msg.obj = bim;
msg.what = 1;
// 把消息发送到主线程的消息队列。
handler.sendMessage(msg);
} else {
// Toast.makeText(MainActivity.this, "对不起,请求失败",
// 0).show();
Message msg = handler.obtainMessage();
handler.sendMessage(msg);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
t.start();
}
}
public String getFileName(String path) {
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
将文件读取流封装成一个工具类: 从输入流中直接读取到数据
package com.zh.htmlv.utls;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
//从输入流中得到文本。
public class Utils {
public static String getTextFromStream(InputStream is) {
byte[] b = new byte[1024];// 读入的数据 要写出来
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream(); //字节数组数组输出流。
try {
while ((len = is.read(b)) != -1) {
bos.write(b, 0, len);
}
// 此时所有的数据都存在字节数组输出流中 把字节数组输出流转换成字节数组
String text = new String(bos.toByteArray(),"gbk"); 这个地方可以改变客户端的编码表 需要注意的是Android开发客户端用utf-8改变服务器的。
return text;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}