天天看點

安卓基礎幹貨(四):安卓網絡程式設計的學習網絡HTML檢視器線程不能修改UI界面消息處理機制的原理(重點)網圖檔檢視器(重點)消息處理常用API新聞用戶端使用smartImageView顯示新聞圖檔smartImageView的工作原理使用GET方式向伺服器端送出資料使用POST方式送出資料(重點)

網絡HTML檢視器

httpurlconnection:
   1、發送請求
    (1)建立一個URL對象
    (2)設定請求頭資訊
   2、伺服器傳回資料
    (1)判斷狀态碼:200 ok,404 沒有找到資源、503、509 伺服器端錯誤
    (2)解析伺服器傳回的二進制資料,解析成一個圖檔
    (3)把圖檔顯示在TextView上           

複制

示例代碼:

package com.itheima.htmlview;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.itheima.htmlview.utils.StreamTools;

public class MainActivity extends Activity {

    private EditText et_path;
    private TextView tv_content;

    private Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            String data = (String) msg.obj;

            tv_content.setText(data);
        };
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_path = (EditText) findViewById(R.id.et_path);

        tv_content = (TextView) findViewById(R.id.tv_content);
        }

    public void click(View view){
        new Thread(){
            public void run() {

                try {
                    String path = et_path.getText().toString().trim();
                    //1、發送請求
                    //(1)建立一個URL對象
                    URL url = new URL(path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    //(2)設定請求頭資訊
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(3000);
                    //2、伺服器傳回資料
                    //(1)判斷狀态碼:200 ok,404 沒有找到資源、503、509 伺服器端錯誤

                    int code = conn.getResponseCode();
                    if(code == 200){
                    //(2)解析伺服器傳回的二進制資料,解析成一個字元串
                        InputStream is = conn.getInputStream();
                        String data = StreamTools.readStream(is);

                        //(3)把圖檔顯示在TextView上
                        Message msg = Message.obtain();
                        msg.obj = data;
                        handler.sendMessage(msg);
                    }else{
                        Toast.makeText(MainActivity.this, "請輸入一個HTML頁面的網絡位址", 0).show();
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();  
    }
}           

複制

線程不能修改UI界面

activity中的oncreate方法和單擊事件的方法都是運作在主線程中的。

隻有建立UI界面的那個線程才能修改UI: Only the original thread that created a view hierarchy can touch its views.

主線程(UI線程),隻有主線程才能修改UI。如果子線程修改UI,系統驗證目前線程是不是主線程,如果不是主線程,就會終止運作。

runOnUiThread

消息處理機制的原理(重點)

步驟:

1、主線程中建立handler

private Handler handler = new Handler(){

};           

複制

2、線上程中得到handler的引用,調用發送消息的方法,

Message msg = new Message();
msg.obj = bm;
handler.sendMessage(msg);           

複制

3、handler修改UI界面

//用來接收消息并處理消息
@Override
public void handleMessage(Message msg) {
    super.handleMessage(msg);
    //2、handler修改UI界面
    Bitmap bm = (Bitmap) msg.obj;
    iv.setImageBitmap(bm);
}           

複制

Handler、Message、Looper(消息處理機制的原理):

前提知識:

所有使用UI界面的作業系統,背景都運作着一個死循環,在不停的監聽和接收使用者發出的指令,一旦接收指令就立即執行。

當我們的Android應用程式的程序一建立的時候,系統就給這個程序提供了一個Looper,Looper是一個死循環,它内部維護這個一個消息隊列,Loop不停地從消息隊列中取消息(Message),取到消息就發送給了Handler,最後Handler根據接收到的消息去修改UI。

網圖檔檢視器(重點)

1、發送請求:GET

2、接收伺服器端傳回的響應資料

使用代碼實作的步驟:

1、建立URL,打開一個HTTP的連接配接;
2、設定請求頭資訊:GET(GET、POST)
3、接收伺服器端傳回的響應資料,響應碼:200 ok,404沒有找到資源 ,503伺服器端内部錯誤
4、把接收的二進制資料轉換成圖檔           

複制

模版代碼:

1、建立一個URL對象,打開一個HTTP連接配接
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();

2、設定請求頭資訊:GET(GET、POST)

    conn.setRequestMethod("GET");
    conn.setConnectTimeout(5000);

3、接收伺服器端傳回的響應資料,響應碼:200 ok,404沒有找到資源 ,503伺服器端内部錯誤

    int code = conn.getResponseCode();
    if(code == 200){
        InputStream is = conn.getInputStream();
    }
4、添加通路網際網路的權限:
    <uses-permission android:name="android.permission.INTERNET"/>

網絡在主線程上的異常: android.os.NetworkOnMainThreadException
從Androi4.0開始,google更加UI界面運作的流暢性,強制要求通路網絡的操作不能在主線程中進行,隻能在子線程中進行。           

複制

消息處理常用API

//運作在主線程上,内部使用線程的合并技術,把資料子線程合并了主線程
runOnUiThread(new Runnable() {      
    @Override
    public void run() {
        iv.setImageBitmap(bm);
    }
});

//Runnable 線程的接口類,uptimeMillis從開機到現在間隔的毫秒數
handler.postAtTime(new Runnable() {
    @Override
    public void run() {
        tv.setText("postAtTime");
    }
},  5000);

Timer計時器           

複制

新聞用戶端

1、設計UI界面

(1)activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >

<ListView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/lv" />

</LinearLayout>           

複制

(2)item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<com.loopj.android.image.SmartImageView
    android:layout_width="80dip"
    android:layout_height="80dip"
    android:src="@drawable/ic_launcher"
    android:id="@+id/iv_image"
    />

<TextView 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/tv_title"
    android:singleLine="true"
    android:text="标題标題标題标題标題标題标題标題标題"
    android:layout_toRightOf="@id/iv_image"
    android:textSize="16sp"
    />

<TextView 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/tv_desc"
    android:maxLines="3"
    android:text="描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述"
    android:layout_toRightOf="@id/iv_image"
    android:layout_below="@id/tv_title"
    android:textSize="12sp"
    />
<TextView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/tv_type"
    android:layout_alignParentRight="true"
    android:text="跟帖"
    android:layout_below="@id/tv_desc"
    android:textSize="10sp"
    />

</RelativeLayout>           

複制

2、在子線程中通路網絡,獲得xml資料

public class MainActivity extends Activity {

    private ListView lv;
    private List<NewsItem> list = new ArrayList<NewsItem>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        lv = (ListView) findViewById(R.id.lv);
        //從網絡上讀取資料、解析xml檔案
        readData();
        //使用資料擴充卡為listview填充資料
        lv.setAdapter(new MyAdapter());
    }

    //從網絡上讀取資料、解析xml檔案
    private void readData(){
        //在子線程中通路網絡,獲得xml資料
        new Thread(){
            public void run() {
                try {
                    String path = "http://192.168.22.136:8080/news.xml";
                    //1、發送請求
                    //(1)建立一個URL對象
                    URL url = new URL(path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    //(2)設定請求頭資訊
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(3000);
                    //2、伺服器傳回資料
                    //(1)判斷狀态碼:200 ok,404 沒有找到資源、503、509 伺服器端錯誤 
                    int code = conn.getResponseCode();
                    if(code == 200){
                    //(2)解析伺服器傳回的二進制資料,解析成一個字元串
                        InputStream is = conn.getInputStream();
                        list = NewsParseService.parseNewsItems(is);
                    }else{
                        Toast.makeText(MainActivity.this, "請輸入一個HTML頁面的網絡位址", 0).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }
}           

複制

3、解析xml格式的資料,把資料封裝到list

public class NewsParseService {

    private static List<NewsItem> list;

    /**
    * 解析新聞的xml資料傳回list
    * @param is
    * @return
    */
    public static List<NewsItem> parseNewsItems(InputStream is){

        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(is, "UTF-8");

            //得到解析的時間類型
            int type = parser.getEventType();
            list = new ArrayList<NewsItem>();

            NewsItem item = null;
            while(type != XmlPullParser.END_DOCUMENT){

                switch (type) {

                case XmlPullParser.START_TAG:

                    if("item".equals(parser.getName())){
                        item = new NewsItem();
                    }else if("title".equals(parser.getName())){
                        String title = parser.nextText();
                        item.setTitle(title);
                    }else if("description".equals(parser.getName())){
                        String description = parser.nextText();
                        item.setDescription(description);
                    }else if("image".equals(parser.getName())){
                        String image = parser.nextText();
                        item.setImage(image);
                    }else if("type".equals(parser.getName())){
                        String newsType = parser.nextText();
                        item.setType(newsType);
                    }else if("comment".equals(parser.getName())){
                        String comment = parser.nextText();
                        item.setComment(comment);
                    }

                    break;

                case XmlPullParser.END_TAG:
                    if("item".equals(parser.getName())){
                    list.add(item);
                    item = null;
                }
            }
            type = parser.next();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(list.size()+"。。。。。。。");
        return list;
    }
}           

複制

4、把list裡面的資料顯示在listview

1、自定義一個資料擴充卡
private class MyAdapter extends BaseAdapter{

    @Override
    public int getCount() {
        return list.size();
    }


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View view = null;
        if(convertView == null){    
            //加載item的布局檔案
            view = View.inflate(MainActivity.this, R.layout.item, null);
        }else{
            //重複利用item的視圖
            view = convertView;
        }

        //得到item布局檔案中的控件
        SmartImageView image = (SmartImageView) view.findViewById(R.id.iv_image);
        TextView tv_title = (TextView) view.findViewById(R.id.tv_title);
        TextView tv_desc = (TextView) view.findViewById(R.id.tv_desc);
        TextView tv_type = (TextView) view.findViewById(R.id.tv_type);

        NewsItem item = list.get(position);

        image.setImageUrl(item.getImage());
        //設定item中的控件設定資料
        tv_title.setText(item.getTitle());

        tv_desc.setText(item.getDescription());

        if("1".equals(item.getType())){
            tv_type.setText("跟帖:"+item.getComment());
            tv_type.setTextColor(Color.BLACK);
        }else if("2".equals(item.getType())){
            tv_type.setText("視訊");
            tv_type.setTextColor(Color.BLUE);
        }else if("3".equals(item.getType())){
            tv_type.setText("專題");
            tv_type.setTextColor(Color.RED);
        }
        return view;
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return 0;
    }
}
2、在oncreate方法中調用lv.setAdapter方法為listview填充資料:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    lv = (ListView) findViewById(R.id.lv);

    //從網絡上讀取資料、解析xml檔案
    readData();
    //使用資料擴充卡為listview填充資料
    lv.setAdapter(new MyAdapter());
}           

複制

使用smartImageView顯示新聞圖檔

  • 1、把smartImageView的源代碼/src/com檔案夾拷到自己的代碼的src目錄中
  • 2、調用image.setImageUrl(item.getImage())方法加載圖檔

imageLoader 開源的項目

不重複發明輪子 1減少開發成本 耗時

smartImageView的工作原理

1、寫一個類繼承ImageView

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.widget.ImageView;

public class SmartImageView extends ImageView{


    //用來建立帶有屬性資訊和樣式的對象
    public SmartImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    //用來建立帶有屬性資訊的對象
    public SmartImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //用來建立一個沒有屬性的對象
    public SmartImageView(Context context) {
        super(context);
    }

    //1、在主線程中建立一個handler
    private Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            //2、修改UI界面
            Bitmap bm = (Bitmap) msg.obj;
            SmartImageView.this.setImageBitmap(bm);
        };
    };

    //寫一個setImageUrl方法,可以根據一個圖檔的url加載圖檔
    public void setImageUrl(final String url){

        new Thread(){
            public void run() {

                try {
                    String path = url;
                    //1、發送請求
                    //(1)建立一個URL對象
                    URL url = new URL(path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    //(2)設定請求頭資訊
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(3000);
                    //2、伺服器傳回資料
                    //(1)判斷狀态碼:200 ok,404 沒有找到資源、503、509 伺服器端錯誤

                    int code = conn.getResponseCode();
                    if(code == 200){
                    //(2)解析伺服器傳回的二進制資料,解析成一個字元串
                        InputStream is = conn.getInputStream();
                        Bitmap bm =  BitmapFactory.decodeStream(is);
                        //線上程中調用handler的引用,向服務端發送消息
                        Message msg = Message.obtain();
                        msg.obj = bm;
                        handler.sendMessage(msg);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }
}           

複制

2、在布局檔案中添加一個SmartImageView這個類:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
<--報名要寫完整-->
<com.itheima.smartimageview.SmartImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/iv" />

</RelativeLayout>           

複制

使用GET方式向伺服器端送出資料

Get 1、把需要送出的參數組拼到URL位址的後面:

http://192.168.22.136:8080/web/servlet/LoginServlet?username=123&password=1233           

複制

缺點:

1、送出資料的長度有限制:
 最大長度4kb,windows中送出資料時最大長度為1kb;
2、不安全           

複制

優點:

代碼簡單           

複制

代碼:

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import com.itheima.htmlview.utils.StreamTools;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    private EditText et_qq;

    private EditText et_pwd;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_qq = (EditText) findViewById(R.id.et_qq);

        et_pwd = (EditText) findViewById(R.id.et_pwd);

    }

    public void login(View view){
        final String qq = et_qq.getText().toString().trim();

        final String pwd = et_pwd.getText().toString().trim();

        if(TextUtils.isEmpty(qq) || TextUtils.isEmpty(pwd)){
            Toast.makeText(this, "qq和密碼不能空", 0).show();
            return;
        }else{
            new Thread(){

                public void run() {

                    try {
                        //組拼參數到URL後面
                        String path = "http://192.168.22.136:8080/web/servlet/LoginServlet?username="+qq+"&password="+pwd;
                        //1、發送請求
                        //(1)建立一個URL對象
                        URL url = new URL(path);
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        //(2)設定請求頭資訊
                        conn.setRequestMethod("GET");
                        conn.setConnectTimeout(3000);
                        //2、伺服器傳回資料
                        //(1)判斷狀态碼:200 ok,404 沒有找到資源、503、509 伺服器端錯誤

                        int code = conn.getResponseCode();
                        if(code == 200){
                            //(2)解析伺服器傳回的二進制資料,解析成一個字元串
                            InputStream is = conn.getInputStream();
                            String result = StreamTools.readStream(is);
                            System.out.println("---------"+ result);
                         }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                };
            }.start();
        }
    }
}

public class StreamTools {
/**
 * 把輸入流轉換成一個字元串
 * @param is
 * @return
 */
    public static String readStream(InputStream is){

        try {
            byte[] buffer = new byte[1024];
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            StringBuilder sb = new StringBuilder();
            int len = -1;
            while((len = is.read(buffer)) != -1){
                baos.write(buffer, 0, len);
            }

            return baos.toString();
        } catch (Exception e) {
            return "";
        }   
    }
}           

複制

使用POST方式送出資料(重點)

業務場景:

1、使用者登入
2、檔案上傳           

複制

1、設定請求頭資訊POST、Content-Length:

conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//必須添加的兩個請求頭資訊
conn.setRequestProperty("Content-Length", data.length()+"");
conn.setRequestMethod("POST");           

複制

2、設定把資料送出到伺服器端:

conn.setDoOutput(true);
//把資料寫到伺服器端
conn.getOutputStream().write(data.getBytes());           

複制

缺點:

1、代碼複雜           

複制

優點:

1、安全;
2、送出大量的資料           

複制

代碼:

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import com.itheima.htmlview.utils.StreamTools;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    private EditText et_qq;

    private EditText et_pwd;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_qq = (EditText) findViewById(R.id.et_qq);

        et_pwd = (EditText) findViewById(R.id.et_pwd);

    }

    public void login(View view){
        final String qq = et_qq.getText().toString().trim();

        final String pwd = et_pwd.getText().toString().trim();

        if(TextUtils.isEmpty(qq) || TextUtils.isEmpty(pwd)){
            Toast.makeText(this, "qq和密碼不能空", 0).show();
            return;
        }else{
            new Thread(){

                public void run() {

                    try {
                        String path = "http://192.168.22.136:8080/web/servlet/LoginServlet";
                        String data = "username="+qq+"&password="+pwd;
                        //1、發送請求
                        //(1)建立一個URL對象
                        URL url = new URL(path);
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        //(2)設定請求頭資訊
                        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

                        //必須添加的兩個請求頭資訊
                        conn.setRequestProperty("Content-Length", data.length()+"");
                        conn.setRequestMethod("POST");
                        conn.setConnectTimeout(3000);
                        //設定把資料送出到伺服器端
                        conn.setDoOutput(true);
                        //把資料寫到伺服器端
                         conn.getOutputStream().write(data.getBytes());

                        //2、伺服器傳回資料
                        //(1)判斷狀态碼:200 ok,404 沒有找到資源、503、509 伺服器端錯誤

                        int code = conn.getResponseCode();
                        if(code == 200){
                            //(2)解析伺服器傳回的二進制資料,解析成一個字元串
                            InputStream is = conn.getInputStream();
                            String result = StreamTools.readStream(is);
                            System.out.println("---------"+ result);
                         }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                };
            }.start();
        }
    }
}


get:送出資料大小是有限制 1Kb   4kb 
post:           

複制