一、网络编程的核心步骤
(1)在清单AndroidManifest.xml文件中添加INTERNET权限。
(2)连接到网络地址的代码
第1步:创建URL
String path = "http://192.168.17.98:8080/img/news.xml";
URL url = new URL(path);
第2步:通过URL获得连接HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
第3步:通过HttpURLConnection连接设置一些请求的参数
conn.setRequestMethod("GET"); //默认请求方式就是GET,要大写。
conn.setConnectTimeout(5); //链接网络超时时间,秒作单位。
第4步:请求响应并获取服务器的响应码,判断响应码的状态,采用相应的动作。
int code = conn.getResponseCode(); /*200 代表获取服务器资源全
部成功 206 请求部分资源*/
if (code == 200) {
//解析连接的输入流,获取数据,进行其它操作。
...
}
通过查看API发现,HttpURLConnection及其父类,没有close这个关闭连接的方法。
二、Android中的消息机制
在Android4.0之后,google工程师强制要求Android中的耗时操作(如上面的网络访问、拷贝
错误:
android.os.NetworkOnMainThreadException 在主线程访问网络
然而,在子线程中往往会有更新UI的操作(如改变activity中的组件的text值),但是更新UI
错误:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original
thread that created a view hierarchy can touch its views. 只有主线程才可以更新ui
耗时操作不能放在UI线程,子线程不能更新UI)
呢?解决办法:有2种方式:
第1种:消息机制Handler
1)在类的成员位置上创建一个Handler对象,复写它的handleMessage(Message msg)方法。
在这个方法中获取子线程传递过来的Message,然后更新UI。
//创建助手Handler
private Handler handler = new Handler(){
public void handleMessage(aMessage msg) {
//获取消息的类型
switch (msg.what) {
case LOADSUCESS: //代表获取数据成功
//把数据取出来
String content = (String) msg.obj; //要强转
//显示源码的内容
tv_content.setText(content);
break;
case LOADERROR: //代表获取数据失败
Toast.makeText(getApplicationContext(), "访问的资源不存在
", 1).show();
break;
case LOADEXCEPTION:
Toast.makeText(getApplicationContext(), "服务器忙!!!", 1)
.show();
break;
}
};
};
2)在子线程中调用Handler对象的sendMessage(Message msg)方法,将获取的数据封装到
Message中去。
//创建message对象
Message msg = Message.obtain(); //这种方式得到Message对象,可以减少
创建Message的次数。还可以new个对象
msg.what = LOADSUCESS; //标记Message的类型,int型。
msg.obj =content; //这个属性可以携带任何数据类型
//发送一条消息 告诉系统我要更新ui handleMessage方法会立刻执行
handler.sendMessage(msg);
在Handler类的内部,有一个死循环Looper,一直在监听Handler中的消息队列。在子线程中一发送消息,加入Handler的消息队列,死循环得知就取出消息发送给handMessage方法。原理图如下:
第2种:runOnUiThread(Runnnable action)方法
这是Activity类的一个方法,关于这个方法,Android官方文档说得很明白:
如果当前线程是UI线程(主线程),那么action就会立即执行。如果当前线程不是UI线程,
那么action就会放入UI线程的事件队列,也就是说它会被UI线程所执行。那么就可以把更新UI的
方法放在这个action里面。
核心代码:
//更新ui
runOnUiThread(new Runnable() {
public void run() {
tv_content.setText(content);
}
});
但是在子线程一定不能更新UI吗?答案是可以。
在程序运行之后,Android系统会自动开启一个审计系统,来监听子线程中是否有更新UI的
动作。如果在子线程中有更新UI的动作,很简短很快的话,就不会被审计系统捕捉到,那么就
不会报错。如果用SystemLock.sleep()模拟一个即使是毫秒级的耗时操作的话,也会报错。
三、实现网络图片的缓存
由于图片的加载是十分的消耗流量的,所以初次加载图片时可以将图片保存到缓存中,再从缓
存中把图片显示到控件上,以后每次访问图片的时候先去找缓存中有没有这个图片,缓存中如果没
有再去网络上加载图片至缓存。
Base64和BitmapFactory。
Base6是一个实用工具类,可以将byte[]编码成String,也可以将String解码成byte[]。
decodeToString(byte[] input,int flags):即可将字节数组变成String。
BitmapFactory顾名就是处理Bitmap的工具类,它可以从各种资源包括文件、流、字节数组来
创建一个Bitmap对象。
decodeStream(InputStream in):将流解析成Bitmap对象。
decodeFile(String path):将文件的路径解析成Bitmap对象。
图片缓存的实现流程:
第1步:创建缓存图片文件的File对象
//通过Base64将图片的url地址解码成字节数组,再编码为String类型。
File file =new File(getCacheDir(), Base64.encodeToString(path.getBytes(), Base64.DEFAULT));
第2步:判断File对象是否存在且大小是否为0.
if(file.exists() && file.length() > 0)
{
//通过BitmapFactory的解析路径资源的方法获取Bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
//将Bitmap发送给消息助手,用于更新UI。
Message msg = Message.obtain();
msg.obj = bitmap;
msg.what = LOADCACHE;
handler.sendMessage(msg);
}
else
{
//连接网络,得到网络图片的流,然后对象写入到File图片缓存文件中去。
...
InputStream in = conn.getInputStream();
//将流写入缓存中
OutputStream out = new FileOutputStream(file);
int len = 0;
byte[] buf = new byte[1024];
while((len = in.read(buf))!= -1 )
{
out.write(buf,0,len);
}
in.close();
out.close();
//读取图片缓存文件,得到Bitmap对象。
//将Bitmap发送给消息助手,用于更新UI。
Message msg = Message.obtain();
msg.obj = BitmapFactory.decodeFile(file.getAbsolutePath());
msg.what = LOADNET;
handler.sendMessage(msg);
}
四、延迟与定时操作
1) Handler类本身有个方法可以实现延迟操作
public final boolean postDelayed (Runnable r, long delayMillis)
官方文档是这么说明这个方法的:
将Runnable加入消息队列,过了指定的时间delayMillis之后运行。Runnable会运行在
Handler对象绑定的那个线程中。
handler.postDelayed(new Runnable() {
@Override
public void run() {
tv.setText("123456");
}
}, 5000);
//上面这段代码的handler必需是成员位置已经创建的Handler对象,直接写new Handler会报下
面这个异常。
java.lang.RuntimeException: Can't create handler inside thread that has not called
Looper.prepare()
原因是上面这段代码是写在一个子线程中的,如果直接new Handler,这个Handler对象属
于子线程的内部成员,Runnable还是在子线程中执行的,仍然在子线程中更新UI。
当然了,Handler类还有定时操作的API,用时再去查吧。
2) Timer实现延迟操作
在JDK里,有个类Timer,可以实现延迟与定时操作,以及重复执行操作。
schedule(TimerTask
安排在指定延迟后执行指定的任务。
//定义一个时钟
Timer timer = new Timer();
//延迟执行时钟的run方法
timer.schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv.setText("123456");
}
});
}
},5000);