天天看点

安卓基础干货(四):安卓网络编程的学习网络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:           

复制