笔者原以为,从后端的角度来讲,android和java客户端程序是大同小异的。
然而笔者有一次写一个爬取网页链接的小demo时,发现还是有不少区别的。
Android.os.NetworkOnMainThreadException异常产生的原因
Android4.0 以后不允许在主线程进网络连接,否则会出现 Android.os.NetworkOnMainThreadException。因此,必须另起一个线程进行网络连接方面的操作。
贴上代码
public class NetworkConnect {
public static Gson gson = new Gson();
public static String token = "5de492fe8fff9510a2c0e84f250c669557502862";
public static String getJson(String httpUrl, String httpArg) {
MyThread t = new MyThread(httpUrl,httpArg);
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread stop");
String result =t.getString();
return result;// 通过out.Stream.toByteArray获取到写的数据;
}
private static class MyThread extends Thread{
ByteArrayOutputStream outStream;
String httpUrl;
String httpArg;
public MyThread(String httpUrl, String httpArg) {
outStream = new ByteArrayOutputStream();
this.httpUrl=httpUrl;
this.httpArg=httpArg;
}
@Override
public void run() {
byte[] data = new byte[1024];
int len = 0;
URL url;
try {
if (httpUrl.contains("github")) {
if (httpArg.contains("?"))
url = new URL(httpUrl + httpArg + "&access_token=" + token);
else
url = new URL(httpUrl + httpArg + "?access_token=" + token);
} else {
url = new URL(httpUrl + httpArg);
}
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStream inStream = conn.getInputStream();
while ((len = inStream.read(data)) != -1) {
outStream.write(data, 0, len);
}
inStream.close();
System.out.println("subthread stop");
} catch (MalformedURLException e) {
e.printStackTrace();
// TODO Auto-generated catch block
} catch (IOException e) {
e.printStackTrace();
// TODO Auto-generated catch block
}
}
public String getString(){
return new String(outStream.toByteArray());
}
}
如图所示,将主要的获取json的Http请求放到子线程MThread中,将数据请求异步进行,可以解决上面问题。
PS:这是很久前的代码了,因为数据量很少,基本能够保证子线程在主线程之前返回完整数据。这样其实相当于仅仅解决异常。如果更合理一些的话,应当在子线程中以Message方式通知主线程数据获取完毕。
//采用传送消息的模式 把view操作消息发给主线程
Message msg = new Message();
msg.what=DATA_FIN;
msg.obj=new String(outStream.toByteArray());
handler.sendMessage(msg);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
Toast.makeText(MainActivity.this, "访问网络失败",0).show();
}
}
}.start();
然后在主线程中定义handler
private Handler handler =new Handler(){
public void handleMessage(android.os.Message msg)
{
if(msg.what==DATA_FIN)
{
//Do Something
.......
}
};
};
如此保证多线程中通信完毕。
附上顺序图(暂时上传不上去)
其他方法
1、把android:targetSdkVersion=”Version Num”这句话从AndroidManifest.xml去掉,问题就解决了,但不是治本的解决方案
2、在进行网络通信的部分,单独执行异步任务
创建一个异步的任务类,异步任务类里包含网络消息队列(Queue)。当要发送消息的时候(也就是原来进行网络操作的地方,我们假设是发送消息),我们将网络消息加入Queue,在异步任务里面用一个while循环查询Queue,当Queue中有数据的时候就进行发送,没有的话,sleep一段时间。
附上代码
public class SendThread extends AsyncTask{
private static Queue queue = new LinkedList();
//发送请求消息
public static Boolean SendOrder(String Order){
queue.offer(Order);
return true;
}
//后台循环处理
@Override
protected Object doInBackground(Object... params) {
String string;
while (true) {
if ((string = queue.poll()) != null) {
//若有队列中有消息,就异步发送
UdpHelper.sendReally(string);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
3、可以再Activity的onCreate()方法中加入这样一段代码,如下:
if (Build.VERSION.SDK_INT >= 11) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
}
可以忽略如NetworkOnMainThreadException这样的限制策略