Android中的http通信

一、什麼是http協議

超文本傳送協議(HTTP-Hypertext transfer protocol)定義了瀏覽器(既萬維網客戶進程)怎樣向萬維網服務器請求萬維網文檔,以及服務器怎樣把文檔傳送給瀏覽器。從層次的角度看,HTTP是面向(transaction-oriented)應用層協議,它是萬維網上能夠可靠地交換文件(包括文本、聲音、圖像等各種多媒體文件)的重要基礎。

1、工作流程
一次HTTP操作稱作爲一個事務,其工作過程可分爲四步:
1)首先客戶機與服務器需要建立連接,只要單擊某個超級鏈接,HTTP的工作開始。
2)建立連接後,客戶機發送一個請求給服務器,請求方式的格式爲:統一資源標識符(URL)、協議版本號,後面是MIME信息包括請求修飾符、客戶機信息和可能的內容。
3)服務器接收到請秋後,給予相應的響應信息,其格式爲一個狀態行,包括信息的協議版本號、一個成功或錯誤的代碼,後邊是MIME信息包括服務器信息、實體信息和可能的內容。
4)客戶端接收服務器所返回的信息通過瀏覽器顯示在用戶的顯示屏上,然後客戶機與服務器斷開鏈接。

如果在以上過程中的某一步出現錯誤,那麼產生錯誤的信息將返回到客戶端,有顯示屏輸出。對於用戶來說,這些過程是由HTTP自己完成的,用戶只要用鼠標點擊,等待信息顯示就可以了。
HTTP協議永遠都是客戶端發起請求,服務器回送響應。
這樣就限制了使用HTTP協議,無法實現在客戶端沒有發起請求的時候,服務器將消息推送給客戶端。
HTTP協議是一個無狀態的協議,同一個客戶端的這次請求和上次請求是沒有對應關係。

2、URL
統一資源定位符,也被稱爲網頁地址,是因特網網上標準的資源的地址。
URL的格式由下列三部分組成:
第一部分是協議(或稱爲服務方式);
第二部分是存有該資源的主機IP地址(有時也包括端口號);
第三部分是主機資源的具體地址,如目錄和文件名等。
第一部分和第二部分之間用“://“符號隔開,第二部分和第三部分用”/“符號隔開,第一和第二部分是不可缺少的,第三部分有時可以忽略。現在幾乎所有的URI都是URL

3、SYN
是TCP/IP建立連接時使用的握手信號,在客戶機和服務器之間建立正常的TCP網絡連接時,客戶端首先發出一個SYN消息,服務器使用SYN+ACK應答標識接收到了這個消息,最後客戶機再以ACK消息響應。這樣在客戶機和服務器之間才能建立起可靠的TCP連接,數據纔可以在客戶機和服務器之間傳遞。

4、ACK
即確認字符,在數據通信中,接收站發給發送站的一種傳輸類控制字符。標識發來的數據已確認接收無誤。
三次握手

5、http請求方式
這裏寫圖片描述

二、HttpURLConnection的介紹

public class HttpThread extends Thread{
    private String url;
    private WebView webView;
    private Handler handler;

    public HttpThread(String url,WebView webView,Handler handler){
        this.url = url;
        this.webView = webView;
        this.handler = handler;
    }

    @Override
    public void run() {
        try {
            URL httpUrl = new URL(url);
            try {
                HttpURLConnection connection = (HttpURLConnection) httpUrl.openConnection();
                connection.setReadTimeout(5000);//設置讀取的超時時間
                connection.setRequestMethod("GET");//網絡請求方式
                //拿到網頁回傳的信息
                final StringBuffer sb = new StringBuffer();
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String str;
                while ((str = reader.readLine())!=null){
                    sb.append(str);
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        webView.loadData(sb.toString(),"text/html;charset=utf-8",null);
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

    }
}

main文件

    public WebView webView;
    private Handler handler= new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = (WebView) findViewById(R.id.webView);

        new HttpThread("http://www.baidu.com",webView,handler).start();
    }

2、加載網絡圖片信息
(1)拿到圖片二進制流InputStream
(2)判斷sd卡是否存在->存在則判斷文件輸出目錄在哪->通過該流將文件下載到本地的目錄
(3)下載完後,在本地進行解析,將本地文件轉換成bitMap

三、http傳遞參數信息和傳遞方式post、get

1、doPost()發送的數據通過output發送,而非url,這樣只需在outout時,將我們要發送的實體數據轉化成字節
2、發送數據量較少,一般限制幾k:doGet()所有數據通過url暴露,有安全問題

四、HTTP常見交互返回值的解析

1、什麼是json格式:是一種輕量級的數據交換格式{“name”:“nate”}
personData:json數組
2、ListView具有重用convertView的功能,convertView可能是前後緩衝池中的view,在切換View時,可能從緩衝池中選出已經存在的convertView,會導致新圖片加載前,先顯示舊的圖片

五、Android異步加載

1、如何提高用戶體驗:使用緩存
2、Lru算法:1)Lru:Least Recently Used近期最少使用算法
2)Android提供了LruCache類來實現這個緩存算法

(1)創建LruCache並指定鍵值對類型
(2)初始方法中,獲取當前應用可使用的內存大小,並設置一個緩衝的大小值,初始化LruCache,必須要重寫sizeOf方法在每次存入緩存的時間調用,告訴當前存入對象到底有多大。
(3)獲取圖片時,先從緩存中取出圖片,如果緩存中沒有,則必須去下載
(4)下載模式中,還需將下載完畢的圖片保存到LruCache中

3、如何提高效率
1)ListView滑動停止後才加載可見項
2)ListView滑動時,取消所有加載項

NewsAdapter類

public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{

    private List<NewsBean> mList;
    //轉化一個Layout佈局,最爲每一個item
    private LayoutInflater mInflater;
    private ImageLoader mImageLoader;
    private int mStart,mEnd;
    public static String[] URLS;//保存當前所獲取的所有圖片的URL地址
    private boolean mFirstIn;

    public NewsAdapter(Context context,List<NewsBean> data,ListView listView){
        mList = data;
        mInflater = LayoutInflater.from(context);
        //這樣就只保留一個ImageLoader
        mImageLoader = new ImageLoader(listView);
        URLS = new String[data.size()];
        for(int i=0;i<data.size();i++){
            URLS[i] = data.get(i).newsIconUrl;
        }
        mFirstIn = true;
        //一定要註冊對應的事件
        listView.setOnScrollListener(this);
    }
    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int i) {
        return mList.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }


    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder = null;
        if(view == null){
            viewHolder = new ViewHolder();
            view = mInflater.inflate(R.layout.item_layout,null);
            viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
            viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
            viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
            view.setTag(viewHolder);
        }else {
            viewHolder = (ViewHolder) view.getTag();
        }
            viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);
            String url = mList.get(i).newsIconUrl;
            viewHolder.ivIcon.setTag(url);
//            new ImageLoader().showImageByThread(viewHolder.ivIcon,mList.get(i).newsIconUrl);//使用多線程
//            new ImageLoader().showImageByAsynvTask(viewHolder.ivIcon,url);
            mImageLoader.showImageByAsynvTask(viewHolder.ivIcon,url);
            viewHolder.tvTitle.setText(mList.get(i).newsTitle);
            viewHolder.tvContent.setText(mList.get(i).newsContent);
            return view;
    }

    @Override
    public void onScrollStateChanged(AbsListView absListView, int i) {
        //判斷ListView當前滾動的一個狀態,處於滾動過程,要取消所有正在下載的Task,滾動完畢,根據ListView的首項和尾項,加載之間項目
        //停止狀態
        if(i == SCROLL_STATE_IDLE){
            //加載可見項
            mImageLoader.loadImages(mStart,mEnd);
        }else {
            //停止任務
            mImageLoader.cancelAllTasks();
        }
    }

    @Override
    public void onScroll(AbsListView absListView, int i, int i1, int i2) {
        mStart = i;
        mEnd = i + i1;
        //當前列表第一次顯示,且列表已經畫出
        if(mFirstIn && i2 > 0){
            mImageLoader.loadImages(mStart,mEnd);
            mFirstIn = false;
        }
    }

    class ViewHolder{
        public TextView tvTitle,tvContent;
        public ImageView ivIcon;
    }
}

ImageLoader 類

public class ImageLoader {

    private ImageView mImageView;
    private String mUrl;
    //創建Cache<對象名字,對象>
    private LruCache<String,Bitmap> mCaches;
    private ListView mListView;
    private Set<NewsAsyncTask> mTask;

    public ImageLoader(ListView listView){
        mListView = listView;
        mTask = new HashSet<>();
        //獲取最大可用內存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        //取一部分作爲緩存空間
        int cacheSize = maxMemory/4;
        mCaches = new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //在每次存入緩存的時候調用
                return value.getByteCount();
            }
        };
    }

    //增加到緩存
    public void addBitmapToCache(String url,Bitmap bitmap){
        if(getBitmapFromCache(url) == null){
            mCaches.put(url,bitmap);
        }
    }

    //從緩存中獲取數據
    public Bitmap getBitmapFromCache(String url){
        return mCaches.get(url);
    }



    //子線程不能更新UI所以用handler
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mImageView.getTag().equals(mUrl)){
                mImageView.setImageBitmap((Bitmap) msg.obj);
            }
        }
    };

    public void showImageByThread(ImageView imageView, final String url){
       mImageView = imageView;
        mUrl = url;
        new Thread(){
            @Override
            public void run() {
                super.run();
                Bitmap bitmap = getBitmapFromURL(url);
                Message message = Message.obtain();//通過該方式創建的msg,可以使用現有的及回收掉的msg,提高msg的使用效率
                message.obj = bitmap;
                mHandler.sendMessage(message);
            }
        }.start();
    }
    public Bitmap getBitmapFromURL(String urlString){
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(urlString);
            try {
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                is = new BufferedInputStream(connection.getInputStream());
                bitmap = BitmapFactory.decodeStream(is);
                connection.disconnect();
                return bitmap;
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }finally{
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    //給ListView的每一個Item都設置一張圖
    public void showImageByAsynvTask(ImageView imageView,String url){
        //從緩存中取出對應的圖片
        Bitmap bitmap = getBitmapFromCache(url);
        //如果緩存中沒有,那麼必須去下載
        if(bitmap == null){
           imageView.setImageResource(R.mipmap.ic_launcher);
        }else {
            imageView.setImageBitmap(bitmap);
        }
    }

    public void cancelAllTasks(){
        if(mTask != null){
            for(NewsAsyncTask task:mTask){
                task.cancel(false);
            }
        }
    }


    //將顯示圖片的控制權,不在在getView時觸發下載,而使用ListView滾動時去觸發下載任務
    //用來加載從start到end的所有圖片
    public void loadImages(int start,int end){
        for(int i = start;i<end;i++){
            String url = NewsAdapter.URLS[i];
            //從緩存中取出對應的圖片
            Bitmap bitmap = getBitmapFromCache(url);
            //如果緩存中沒有,那麼必須去下載
            if(bitmap == null) {
                NewsAsyncTask task = new NewsAsyncTask(url);
                task.execute(url);
                mTask.add(task);//將新創建的Task存到集合中,統一做管理
            }else {
                ImageView imageView = (ImageView) mListView.findViewWithTag(url);//url作爲唯一的tag
                imageView.setImageBitmap(bitmap);
            }
        }
    }

    private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
//        private ImageView mImageView;
       private String mUrl;
       public NewsAsyncTask(String url){
//           mImageView = imageView;
           mUrl = url;
       }

        @Override
        protected Bitmap doInBackground(String... strings) {
            String url = strings[0];
            //從網絡獲取圖片
            Bitmap bitmap = getBitmapFromURL(url);
            if(bitmap!=null){
                //將不再緩存的圖片加入緩存
                addBitmapToCache(url,bitmap);
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
            if(imageView != null && bitmap != null){
                imageView.setImageBitmap(bitmap);
            }
            mTask.remove(this);
//            if(mImageView.getTag().equals(mUrl)){
//                mImageView.setImageBitmap(bitmap);
//            }
//            mImageView.setImageBitmap(bitmap);
        }
    }
}

MainActivity類

public class MainActivity extends AppCompatActivity {

    private ListView mListView;
    private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.lv_main);
        new NewsAsyncTask().execute(URL);//將url傳遞進去
    }
    /**
     *將url對應的json格式數據轉化爲我們所封裝的NewsBean
     */
    private List<NewsBean> getJsonData(String url){
        List<NewsBean> newsBeansList = new ArrayList<>();
        try {
            //此句功能與url.openConnection().getInputStream()相同,可根據URL直接聯網獲取網絡數據,簡單粗暴,返回類型爲InputStream
            String jsonString  = readStream(new URL(url).openStream());
            JSONObject jsonObject;
            NewsBean newsBean;
            try {
                jsonObject = new JSONObject(jsonString);
                JSONArray jsonArray = jsonObject.getJSONArray("data");
                for(int i=0;i<jsonArray.length();i++){
                    jsonObject = jsonArray.getJSONObject(i);
                    newsBean = new NewsBean();
                    newsBean.newsIconUrl = jsonObject.getString("picSmall");
                    newsBean.newsTitle = jsonObject.getString("name");
                    newsBean.newsContent = jsonObject.getString("description");
                    newsBeansList.add(newsBean);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return newsBeansList;
    }

    /**
     *通過is解析網頁返回的數組,獲取Json格式字符串(網頁返回的字符串)
     */
    private String readStream(InputStream is){
        InputStreamReader isr;
        String result="";
        try {
            String line = "";
            isr = new InputStreamReader(is,"utf-8");//字節流轉化爲字符流
            BufferedReader br = new BufferedReader(isr);
            while ((line = br.readLine())!=null){
                result += line;
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }
    /**
     * <String類型的請求網址,中間過程,返回結果>
     * 實現網絡的異步訪問
     */
    class NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
        @Override
        //在該方法中獲取URL
        protected List<NewsBean> doInBackground(String... strings) {
            return getJsonData(strings[0]);
        }
        //將生成的newsBeans設置給listView
        @Override
        protected void onPostExecute(List<NewsBean> newsBeans) {
            super.onPostExecute(newsBeans);
            NewsAdapter adapter = new NewsAdapter(MainActivity.this,newsBeans,mListView);
            mListView.setAdapter(adapter);
        }
    }
}

4、總結
1)通過異步加載,避免阻塞UI線程
2)通過LruCache,將已下載圖片放到內存中
3)通過判斷ListView滑動狀態,決定何時加載圖片
4)不僅僅是ListView,任何控件都可以使用異步加載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章