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章的挑战练习中。