一、问题
在安卓2.3版本之前,我们在MainThread里面进行网络操作时没有问题的,但是在2.3版本之后(也就是3.0等),就会出现.NetworkOnMainThreadException异常。举一个例子,比如我们要显示一张网络图片,以csdn的logo为例,可以这样写:
package com.example.netimageviewer; import java.io.IOException; import com.example.netimageviewer.service.ImageUtil; import android.os.Bundle; import android.os.StrictMode; import android.app.Activity; import android.graphics.Bitmap; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends Activity { private Button button; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); imageView = (ImageView) findViewById(R.id.img); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
获取图片的工具类:
package com.example.netimageviewer.service; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import com.example.netimageviewer.util.StreamUtil; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class ImageUtil { public static Bitmap getImage(String addressUrl) throws IOException { URL url = new URL(addressUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); InputStream is = conn.getInputStream(); byte[] b = StreamUtil.getByte(is); Bitmap bf = BitmapFactory.decodeByteArray(b, 0, b.length); return bf; } }
把inputstream转化成一个byte[] 的工具类:
package com.example.netimageviewer.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class StreamUtil { public static byte[] getByte(InputStream is) throws IOException{ ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int len = 0; while ((len = is.read(b)) != -1) { stream.write(b, 0, len); } is.close(); byte[] byteStream=stream.toByteArray(); System.out.println(byteStream.toString()); return byteStream; } }
然后添加上访问网络权限:<uses-permission android:name="android.permission.INTERNET"/>,最后运行程序,如果在2.3版本之前就可以显示出图片,但是在2.3版本之后就会出现.NetworkOnMainThreadException异常。
二、解决方法:
1、一是在主线程的onCreate()方法中加入如下代码:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads().detectDiskWrites().detectNetwork() .penaltyLog().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects().detectLeakedClosableObjects() .penaltyLog().penaltyDeath().build());
注:
通过查阅相关资料,发现,自从Android 2.3之后,系统增加了一个类:StrictMode。这个类对网络的访问方式进行了一定的改变。Android的官方文档给出了这个类设置的目的:StrictMode是一个系统提供的开发工具,用以检测在开发过程中因为偶然的事故从而造成的系统潜在的问题,进而提示开发者对其进行修复。StrictMode通常用于捕获磁盘访问或者网络访问中与主进程之间交互产生的问题,因为在主进程中,UI操作和一些动作的执行是最经常用到的,它们之间会产生一定的冲突问题。将磁盘访问和网络访问从主线程中剥离可以使磁盘或者网络的访问更加流畅,提升响应度和用户体验。
显然,大多数初学者在进行网络开发时,会选择将访问网络的代码直接放到主进程中,由于和主进程的首要工作——UI交互——相矛盾,因此,必须设置一定的检测机制,以保证系统运行的流畅,所有的异常都可以被检测。 使用的时候只需要在你项目运行的入口Activity的OnCreate中放入这段代码,那么整个项目程序都有用。不需要每个Activity里面加入。
但是呢,从StrictMode类的用途我们就可以知道这不是一个好方法。
2、一个比较稳妥的方法就是使用AsyncTask,这个类相信都不陌生。那代码就可以这样来写了:
package com.example.netimageviewer.util; import java.io.InputStream; import java.net.URL; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.widget.ImageView; public class DownImage extends AsyncTask<String, Void, Bitmap> { private ImageView imageView; public DownImage(ImageView imageView) { this.imageView = imageView; } @Override protected Bitmap doInBackground(String... params) { String url = params[0]; Bitmap bitmap = null; try { InputStream is = new URL(url).openStream(); bitmap = BitmapFactory.decodeStream(is); } catch (Exception e) { e.printStackTrace(); } return bitmap; } @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); imageView.setImageBitmap(result); } }
那么MainActivity的代码就应该是这样的了:
package com.example.netimageviewer; import java.io.IOException; import com.example.netimageviewer.util.DownImage; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.android.internal.util.*; import android.widget.ImageView; public class MainActivity extends Activity { private Button button; private ImageView imageView; private String uriCSDN = "http://csdnimg.cn/www/images/csdnindex_logo.gif?20121229"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) this.findViewById(R.id.button); imageView = (ImageView) this.findViewById(R.id.img); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new DownImage(imageView).execute(uriCSDN); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
另外还有朋友建议使用Thread、Handler、Runnable等来实现,当然这也可以,思维灵活,方法多多。