网络访问
Android下访问网络是必须掌握的一门技术,市面上大部分的APP应用基本都必须要与服务器打交道,请求网络,而Android下访问网络有一个便捷的API:HttpURLConnection
网络权限
访问网络属于“侵犯用户利益”行为,因此必须在工程的AndroidManifest.xml中声明对应的权限:<uses-permission android:name="android.permission.INTERNET"/>
访问网络的步骤
1. 创建URL对象
URL url = new URL(path);//path为网络请求的路径
(URL的作用:统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。)
2. 通过URL打开网络连接对象
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
3. 设置网络请求的参数
conn.setRequestMethod("GET");//设置请求参数为get,默认的请求方式就是get
conn.setConnectTimeout(5000);//设置请求服务器的超时时间.
4. 得到服务器的返回信息
String type = conn.getContentType();//服务器返回的数据类型
int length = conn.getContentLength();//服务器返回的数据类型
int code = conn.getResponseCode();// 200 OK 404 资源没找到503服务器内部错误
5. 得到服务器返回的数据
InputStream is = conn.getInputStream()
如果是一张图片资源的话,android提供了一个方便的API:BitmapFactory可以方便将流转为Bitmap位图图片:
Bitmap bitmap = BitmapFactory.decodeStream(IO输入流);
useragent的使用
作用:让服务器端判断客户端是来自 电脑,还是手机设备! 设置方式:
conn.setRequestProperty("User-Agent","Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; InfoPath.2");
通过该设置可以服务器端认为该请求是来自电脑windows系统
ANR异常
Application Not Response,应用程序无响应,简称ANR异常。在主线程中做一些耗时的操作,比如网络访问、文件拷贝等阻塞了主线程,导致主线程无法响应,这时就会报ANR异常。因此对于耗时操作,必须在子线程中完成。
---主线程不能访问网络
Google 工程师考虑到网络访问是一个比较耗时的操作,从Android 4.0开始,Google更加在意UI界面运行的流畅性,强制要求访问网络的操作不允许在主线程中执行,只能在子线程中进行,在主线程请求网络时,会报如下错误:android.os.NetworkOnMainThreadException
因此,访问网络的操作必须在子线程中进行。
---子线程不能修改UI
主线程也叫UI 线程,Activity中的onCreate方法和点击事件的方法都是运行在主线程中的。主线程创建的界面,因此只有主线程才能修改界面,别的线程不允许修改,否则会报如下错误:
CalledFromWrongThreadException: Only the original thread that created a view hierarchy
can touch its views.
注意:既然子线程不能修改主线程的UI,那么我们的子线程如果需要修改UI该怎么办呢?这个时候就应该使用Handler实现子线程与主线程之间的通信了。
Handler机制
Android 的Handler 机制(也有人叫消息机制)目的是为了跨线程通信,也就是多线程通信。之所以需要跨线程通信是因为在Android 中主线程通常只负责UI 的创建和修改,子线程负责网络访问和耗时操作,因此,主线程和子线程需要经常配合使用才能完成整个Android 功能。
Handler机制的核心原理
Handler 机制可以用下图来展示。MainThread 代表主线程,newThread 代表子线程。
MainThread 是Android 系统创建并维护的,创建的时候系统执行了Looper.prepare();方法,该方法内部创建了MessageQueue 消息队列(也叫消息池),该消息队列是Message 消息的容器,用于存储通过handler发送过来的Message。MessageQueue 是Looper 对象的成员变量,Looper 对象通过ThreadLocal 绑定在MainThread 中。因此我们可以简单的这么认为:MainThread 拥有唯一的一个Looper 对象,该Looper 对象有唯一的MessageQueue 对象,MessageQueue 对象可以存储多个Message。
MainThread 中需要程序员手动创建Handler 对象,并覆写Handler 中的handleMessage(Message msg)方法,该方法将来会在主线程中被调用,在该方法里一般会写与UI 修改相关的代码。MainThread 创建好之后,系统自动执行了Looper.loop();方法,该方法内部开启了一个“死循环”不断的去之前创建好的MessageQueue 中取Message。如果一有消息进入MessageQueue,那么马上会被Looper.loop();取出来,取出来之后就会调用之前创建好的handler 对象的handleMessage(Message)方法。
newThread 线程是我们程序员自定new 出来的子线程。在该子线程中处理完我们的“耗时”或者网络访问任务后,调用主线程中的handler 对象的sendMessage(msg)方法,该方法一被执行,内部将msg添加到了主线程中的MessageQueue 队列中,这样就成为了Looper.loop()的盘中餐了,等待着被消费。
上面的过程有点类似黑马Java 基础班学习多线程时的生产者和消费者的过程。newThread 属于生产者,负责生产Message,MainThread 属于消费者。这是一个很复杂的过程,但是Android 显然已经将这种模式给封装起来了,就叫Handler 机制。我们使用时只需要在主线程中创建Handler,并覆写handler 中的handleMessage 方法,然后在子线程中调用handler 的sendMessage(msg)方法即可。
Message的获取
在Hanlder 机制中都需要用到Message 对象,该对象的创建或者获取有多种方式。
1. Message msg = new Message(); 直接创建一个新的Message 对象。
2. Message msg = handler.obtainMessage(); 通过handler 对象获取一个Message 对象,该对象从消息缓存池中获取的,可以提高Message 的使用率,减少垃圾回收次数。
3. Message msg = Message.obtain();该方法最常用,也是方法2的内部实现原理。而该方法内部的源码也是很简单如下:
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
子线程中更新UI的方式
除了正常的利用handler发送一个messgae对象去更新UI外,Android下还有以下三种方式可以在子线程中直接更新UI:
1. Handler的post(Runnable runnable)方法
2. View的post(Runnable runnable)方法
3. Activity的runOnUiThread(Runnable runnable)方法
以上三种方法都是需要传递一个Runnable 对象,该对象会被主线程执行。该方法表面上看跟Message 没任何关系,但是其内部帮我们封装了Message。