1. 网络连接基本
1 //通过指定URL获取原始数据,并返回一个字节流数组。 2 public byte[] getUrlBytes(String urlSpec)throws IOException{ 3 4 //根据传入的字符串参数,创建一个URL对象 5 URL url = new URL(urlSpec); 6 //通过url.openConnection()方法得到HttpUrlConnection对象。 7 HttpURLConnection connection = (HttpURLConnection)url.openConnection(); 8 9 try{ 10 ByteArrayOutputStream out = new ByteArrayOutputStream(); 11 /* 12 * 虽然 HttpURLConnection 对象提供了一个连接,但只有在调用 getInputStream() 方法时 13 * (如果是POST请求,则调用 getOutputStream() 方法),它才会真正连接到指定的URL地址。 14 */ 15 InputStream in = connection.getInputStream(); 16 17 //如果连接失败就抛出错误 18 if(connection.getResponseCode() != HttpURLConnection.HTTP_OK){ 19 throw new IOException(connection.getResponseMessage() + ": with" + urlSpec); 20 } 21 22 //写入 23 int bytesRead = 0; 24 byte[] buffer = new byte[1024]; 25 while((bytesRead = in.read(buffer)) > 0){ 26 out.write(buffer,0,bytesRead); 27 } 28 out.close(); 29 return out.toByteArray(); 30 }finally { 31 connection.disconnect(); 32 } 33 } 34 35 //将getUrlBytes(String)方法返回的结果转换成String 36 public String getUrlString(String urlSpec)throws IOException{ 37 return new String(getUrlBytes(urlSpec)); 38 }
最后记得在AndroidManifest.xml文件中添加联网权限。
2. 线程与主线程
线程是个单一执行序列。单个线程中的代码会逐步执行。所有Android应用的运行都是从主线程开始的。然而,主线程不是线程那样的预定执行序列。相反,它处于一个无限循环的运行状态,等待着用户或系统触发事件的发生。事件触发后,主线程便负责执行代码,以响应这些事件。
事件处理循环让UI代码得以按顺序执行。这可以保证任何事件处理都不会发生冲突,同时代码也能够快速响应执行。
网络连接需要时间,Web 服务器可能需要1~2秒的时间来响应访问请求,文件下载则耗时更久。考虑到这个因素,android 禁止任何主线程网络连接行为。即使强行在主线程中进行网络连接,Android 也会抛出 NetworkOnMainThreadException 异常。
而网络连接相比其他任务更耗时。等待响应期间,用户界面毫无反应,这可能会导致应用无响应(Application Not Responding,ANR)现象发生,也就是一个弹框,要求你关闭应用。
怎样使用后台线程最容易呢?答案就是使用 AsyncTask 类。
3. AsyncTask
AsyncTask是一个抽象类 在继承AsyncTask类的时候指定3个泛型参数 [1] Params 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用 [2] Progress 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位 [3] Result 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型 public class DownloadTask extends AsyncTask<Void,Integer,Boolean> { //这个方法在后台任务开始执行之前调用,用于进行一些界面上的初始化操作 @Override protected void onPreExecute() { progressDialog.show(); //显示进度对话框 super.onPreExecute(); } //当后台任务执行完毕并通过return语句进行返回时,这个方法很快会被调用 @Override protected void onPostExecute(Boolean aBoolean) { progressDialog.dismiss();//关闭进度对话框 if(result){ Toast.makeText(context,"Download successedd",Toast.LENGTH_SHORT).show(); }else { Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show(); } super.onPostExecute(aBoolean); } //当在后台调用了publishProgress(progress...)方法后 此方法很快会被调用 可以对UI进行操作 @Override protected void onProgressUpdate(Integer... values) { //在这里更新下载进度 progressDialog.setMessage("Download"+values[0]+"%"); super.onProgressUpdate(values); } //此方法的代码会在子线程中运行,处理耗时任务用,此方法不可以进行UI操作,但可以调用publishProgress(progress...)来完成 @Override protected Boolean doInBackground(Void... params) { try{ while(true){ int downloadPercent = doDownload(); //这是一个虚构的方法 publishProgress(downloadPercent); if(downloadPercent >= 100) break; } }catch (Exception e){ return false; } return null; } }
AsyncTask的取消
AsyncTask.cancel(boolean) 方法有两种工作模式:粗暴的和温和的。如果调用 cancel(false) 方法,它只是温和地设置 isCancelled() 的状态为 true 。随后, AsyncTask 会检查doInBackground(...) 方法中的 isCancelled() 状态,然后选择提前结束运行。
如果调用 cancel(true) 方法,它会粗暴地终止 doInBackground(...) 方法当前所在的线程。
应该在什么时候、什么地方撤销 AsyncTask 呢?
要看情况了。先问问自己,如果fragment或activity已销毁了或是看不到了, AsyncTask 当前的工作可以停止吗?如果可以,就在onStop(...) 方法里(看不到视图),或者在 onDestroy(...) 方法里(fragment/activity实例已销毁)撤销 AsyncTask 实例。
即使fragment/activity已销毁了(或者视图已看不到了),也可以不撤销 AsyncTask ,让它运
行至结束。不过,这可能会引发潜在的内存泄漏,也可能会出现UI更新问题(因为UI已失效)。如果不管用户怎么操作,一定要保证重要工作的完成,可以考虑其他解决方案,比如使用 Service。
AsyncTask 的替代方案
AsyncTaskLoader:是个抽象Loader,可以使用 AsyncTask 把数据加载工作转移到其他线程上。我们创建的loader类几乎都是 AsyncTaskLoader 的子类。 AsyncTaskLoader 能在不阻塞主线程的前提下获取到数据,并把结果发送给目标对象。
LoaderManager 会帮我们妥善管理loader及其加载的数据。而且, LoaderManager 还负责启动和停止loader,以及管理loader的生命周期。
4. JSON解析
JSON对象是一系列包含在 { } 中的名值对。JSON数组是包含在 [ ] 中用逗号隔开的JSON对象列表。对象彼此嵌套形成层级关系。
son.org API提供有对应JSON数据的Java对象,如 JSONObject 和 JSONArray 。使用JSONObject(String) 构造函数,可以很方便地把JSON数据解析进相应的Java对象。更新fetchItems() 方法执行解析任务。
左边为要解析的JSON数据。
JSONObejct解析代码如下:
private void parseItems(List<GalleryItem> items, JSONObject jsonBody)throws IOException,JSONException{ //得到叫做photos的嵌套JSONObject JSONObject photosJsonObject = jsonBody.getJSONObject("photos"); //得到photos里面的叫photo的JSONArray JSONArray photoJsonArray = photosJsonObject.getJSONArray("photo"); //JsonArray中包含若干个JSONObject, 将这些数据一一取出。 for(int i=0; i<photoJsonArray.length(); i++){ JSONObject photoJsonObject = photoJsonArray.getJSONObject(i); GalleryItem item = new GalleryItem(); item.setId(photoJsonObject.getString("id")); item.setCaption(photoJsonObject.getString("title")); //并不是每个图片都有对应的url_s连接,所以需要添加一个检查。 if(!photoJsonObject.has("url_s")){ continue; } item.setUrl(photoJsonObject.getString("url_s")); items.add(item); } }
Gson解析方式在23章的挑战练习中。