Android网络编程
网络编程预备知识
- 访问网络的Android 应用都必须加上访问互联网的权限 :
android.permission.INTERNET
- 开启子线程执行网络或者耗时的操作
1.凡是对UI 的更新,“耗时”操作等都需要在子线程中进行
android4.0以上强制要求,访问网络的操作不允许在主线程中执行只能在子线程中进行
主线程请求网络会报如下错误:
android.os.NetworkOnMainThreadException
2.ANR异常:
Application Not Response,应用程序无响应。在主线程中做一些耗时的操作,阻塞了主线程,当用户点击其时,主线程无法响应,这是就会出ANR 异常 子线程不能修改UI
主线程也叫UI 线程,别的线程修改UI会报如下错误:
CalledFromWrongThreadException: Only the original thread that created a viewhierarchy can touch its views.
解决方式:
1.使用使用Handler 实现子线程与主线程之间的通信
消息处理机制原理:
所有使用UI 界面的操作系统,后台都运行着一个死循环(Looper),在不停的监听和接收用户发出的指令,一旦接收指令就立即执行。
2.使用activity的runOnUiThread方法更新ui,无论当前线程是否是主线程,都将在主线程执行模拟器访问本地tomcat
localhost/127.0.0.1在模拟器上都是访问模拟器自己,
再模拟器上访问本地电脑需使用android内置IP:0.0.0.2,10.0.0.2
案例 html源码查看器
public class MainActivity extends Activity {
// 定义两个常量用于代表消息的类型
private static final int ERROR = 0;
private static final int OK = 1;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 获取界面的TextView 对象实例
TextView tv = (TextView) findViewById(R.id.tv);
// 如果接收到的信息为OK,则将结果显示出来
if (msg.what == OK) {
tv.setText((String) msg.obj);
// 如果ERROR 则提示错误信息
} else if (msg.what == ERROR) {
tv.setText("对不起,页面加载失败!");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 界面Button 绑定的事件
public void load(View view) {
EditText editText = (EditText) findViewById(R.id.et);
if (TextUtils.isEmpty(editText.getText().toString())) {
Toast.makeText(this, "请输入网页的URL", Toast.LENGTH_SHORT).show();
return;
}
// 调用本类自定义方法,实现网页抓取
getContent(editText.getText().toString());
}
private void getContent(final String path) {
final StringBuilder sb = new StringBuilder();
new Thread(new Runnable() {
@Override
public void run() {
URL url;
try {
// 根据url 地址创建一个URL 对象
url = new URL(path);
// 创建一个HttpURLConnection 对象
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
// 设置连接超时为5 秒
connection.setConnectTimeout(5000);
// 设置读取数据流超时为5 秒
connection.setReadTimeout(5000);
// 设置请求方式为GET
connection.setRequestMethod("GET");
// 开始连接
connection.connect();
// 获取返回状态码
int responseCode = connection.getResponseCode();
if (responseCode == 200) {// 如果成功
// 获取字节流
InputStream inputStream = connection.getInputStream();
// 将字节流转化为BufferedReader
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream));
String tmp = null;
while ((tmp = bufferedReader.readLine()) != null) {
sb.append(tmp);
}
// 新创建个消息对象
Message msg = new Message();
// 设置消息的类型为OK(自定义,用于自己区分不同的消息)
msg.what = OK;
// 给消息绑定数据
msg.obj = sb.toString();
// 将消息发给主线程
handler.sendMessage(msg);
} else {// 如果请求失败
// 发送一个空消息
handler.sendEmptyMessage(ERROR);
}
} catch (Exception e) {
e.printStackTrace();
handler.sendEmptyMessage(ERROR);
}
}
}).start();
}
}
案例 网络图片查看器
public class MainActivity extends Activity {
private ImageView imageView;
// 创建一个Handler 对象,用户接收子线程发送的消息,然后更新UI
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 将数据强转转化为Bitmap,然后显示在ImageView 控件中
imageView.setImageBitmap((Bitmap) msg.obj);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.iv);
}
public void getImage(View view) {
// 开启一个子线程处理网络请求
new Thread(new Runnable() {
@Override
public void run() {
try {
// 从页面获取URL 地址
EditText editText = (EditText) findViewById(R.id.et);
String path = editText.getText().toString();
// 调用Android API 中的TextUtils 工具类判断路径是否为空
if (TextUtils.isEmpty(path)) {
Looper.prepare();
Toast.makeText(MainActivity.this, "请输入URL", 0).show();
Looper.loop();
return;
}
URL url = new URL(path);
// 获取HttpURLConnection 链接对象
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
// 设置请求方法为GET 方式
connection.setRequestMethod("GET");
// 设置链接超时时间
connection.setConnectTimeout(50000);
// 设置输入流读取超时时间
connection.setReadTimeout(50000);
// 打开链接,发送请求
connection.connect();
// 判断返回的状态码
if (connection.getResponseCode() == 200) {
// 获取输入流对象
InputStream inputStream = connection.getInputStream();
// 调用Android API 提供的BitmapFactory 工具类将字节流转化为位图
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// 创建一个新的消息
Message message = new Message();
// 将数据绑定消息
message.obj = bitmap;
// 调用handler 发送消息给主线程
handler.sendMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
案例 新闻页
- 开源第三方图片控件SmartImageView
开源框架SmartImageView 控件用来显示图片。在布局文件声明此控件时需写全包路径,否则将无法找到。
<com.loopj.android.image.SmartImageView
android:id="@+id/iv"
android:layout_height="match_parent"
android:layout_width="60dp"
android:contentDescription="news"
/>
代码
public class MainActivity extends Activity {
// 设置新闻访问地址
private String path = "http://172.16.0.67:8080/news/news.xml";
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
final List<News> list = (List<News>) msg.obj;
// 实例化ListView 对象
ListView listView = (ListView) findViewById(R.id.lv);
// 给ListView 设置适配器
listView.setAdapter(new BaseAdapter() {
@Override
public View getView(int position, View convertView,
ViewGroup parent) {
if (convertView == null) {
convertView = (RelativeLayout) View.inflate(
MainActivity.this, R.layout.item, null);
} else {
return convertView;
}
News news = list.get(position);
// 实例化SmartImageView 对象
SmartImageView imageView = (SmartImageView) convertView
.findViewById(R.id.iv);
// 给SmartImageView 设置一个图像的URL 则SmartImageView 会自动加载图片
imageView.setImageUrl(news.getImage());
TextView connenTextView = (TextView) convertView
.findViewById(R.id.tv_commen);
connenTextView.setText("评论:" + news.getComment());
TextView contentTextView = (TextView) convertView
.findViewById(R.id.tv_content);
contentTextView.setText(news.getDetail());
TextView titleTextView = (TextView) convertView
.findViewById(R.id.tv_title);
titleTextView.setText(news.getTitle());
return convertView;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public int getCount() {
return list.size();
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 调用本类私有方法完成网络访问
getString();
}
// 开启子线程下载数据
private String getString() {
new Thread(new Runnable() {
@Override
public void run() {
XmlPullParserFactory parserFactory;
// 创建一个集合,用于存储新闻
List<News> list = new ArrayList<News>();
try {
// 获取XML 解析器工厂
parserFactory = XmlPullParserFactory.newInstance();
// 获取解析器
XmlPullParser parser = parserFactory.newPullParser();
URL url = new URL(path);
// 打开一个链接
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
// 设置连接超时时间
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setRequestMethod("GET");
// 开始链接
connection.connect();
// 获取数据流
InputStream inputStream = connection.getInputStream();
// 给解析器设置流对象
parser.setInput(new InputStreamReader(inputStream));
News news = new News();
// 解析XML 数据
while (parser.next() != XmlPullParser.END_DOCUMENT) {
int eventType = parser.getEventType();
switch (eventType) {
case XmlPullParser.START_TAG:
String tagName = parser.getName();
if (tagName.equals("newslist")) {
continue;
} else if (tagName.equals("news")) {
news = new News();
System.out.println("创建新的新闻对象");
} else if (tagName.equals("title")) {
String title = parser.nextText();
news.setTitle(title);
} else if (tagName.equals("detail")) {
String detail = parser.nextText();
news.setDetail(detail);
} else if (tagName.equals("comment")) {
String comment = parser.nextText();
news.setComment(comment);
} else if (tagName.equals("image")) {
String image = parser.nextText();
news.setImage(image);
} else {
continue;
}
break;
case XmlPullParser.END_TAG:
String endTag = parser.getName();
if (endTag.equals("news")) {
// 将解析出来的数据添加到集合中
list.add(news);
}
break;
default:
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
Looper.prepare();
Toast.makeText(MainActivity.this, "请求网络失败" + e, 0).show();
Looper.loop();
}
// 如果当前消息池中有则返回一个,没有则创建
Message msg = Message.obtain();
// 给当前消息绑定数据
msg.obj = list;
handler.sendMessage(msg);
}
}).start();
return null;
}
}
Android消息处理机制
Looper、Message、Handler 的关系
当我们的Android 应用程序的进程一创建的时候,系统就给这个进程提供了一个Looper,Looper 是一个死循环,它内部维护这个一个消息队列。Looper 不停地从消息队列中取消息(Message),取到消息就发送给了Handler,最后Handler 根据接收到的消息去修改UI。Handler 的sendMessage 方法就是将消息添加到消息队列中
runOnUiThread
Activity 中提供了一个runOnUiThread 方法,用于进行消息处理。此方法是通过线程合并——join 来实现消息处理的
线程合并:主线程将子线程的任务拿到自己这里来执行并终止子线程
/**
* 在UI 线程中运行我们的任务,如果当前线程是UI 线 程,则立即执行,如果不是则该任务发送到UI 线程的事件队列。
*/
runOnUiThread(new Runnable() {
@Override
public void run() {
// 自定义我们的业务代码
}
});
return null;
postDelayed
该方法是Handler 对象提供的,Handler 给消息队列发送一个消息,发送成功则返回true,否则返回false,如果返回false 一般是由于looper 进程不存在导致的。
该方法主要用于定时任务。如果返回true 也不一定代表着我们的定时任务就执行了,因为很可能在定时任务的时间未到之前我们的looper 进程退出了,那么消息也就丢失了。
执行该任务的线程用的就是Handler 对象所在的线程。
/**
* 该方法将一个Runnable 对象r 添加到消息队列,在指定的时间后会被执行。 这个Runnable 对象会运行在当前handler
* 所在的线程中。 第一个参数:Runnable 要执行的任务 第二个参数:delayMillis(单位:毫秒) runnable
* 任务被执行前的延迟时间 返回值:boolean ,如果该Runnable 被成功添加到消息队列则返回true,否 则返回false
* 不过,通常返回false 是因为looper 进程处理消息队列退出。 注意:返回true 不代表着Runnable
* 被执行,如果looper 在延时任务还没被执 行前退出了,那么消息就丢失掉了。
*/
boolean flag = handler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, 2000);
postAtTime
该方法也属于Handler 对象,
唯一不同的是该方法设置的定时任务是一个绝对时间,
指的是Android 系统的开机时间,
如果想设置从当前时间算起2 秒后执行该任务则可以将时间这样写:SystemClock.uptimeMillis()+2000,其SystemClock.uptimeMillis()是系统运行时间。
boolean postAtTime = handler.postAtTime(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}, SystemClock.uptimeMillis() + 2000);