美文网首页
Chapter 09. 消息机制与异步任务&网络编程

Chapter 09. 消息机制与异步任务&网络编程

作者: GeekGray | 来源:发表于2018-10-12 21:01 被阅读6次

阅读原文

消息机制与异步任务&网络编程

9.1 Handler消息机制与异步任务

  • 在Android中,只有在UIThread中才能直接更新界面,长时间的工作(联网)都需要在workerThread中执行。

  • 在分线程获得服务器数据后, 需要立即到主线程去更新界面显示数据---runOnUiThread()或者Handler。

  • 如何实现线程间通信呢? (主线程 分线程)---消息机制 Handler + Thread

  • 异步任务AsyncTask:实际上是对Handler + Thread的封装;内部提供了线程池


9.2.Handler消息机制原理

Handler机制主要包括四个关键对象,分别是Message、Handler、MessageQueue、Looper

Message

Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。Message的what字段可以用来携带一些整型数据,obj字段可以用来携带一个Object对象。

Handler

Handler就是处理者的意思,它主要用于发送消息和处理消息。一般使用Handler对象的sendMessage()方法发送消息,发出的消息经过一系列的辗转处理后,最终会在传递到Handler对象的handlerMessage()方法中。

MessageQueue

MessageQueue是消息对列,它主要用来存放通过Handler发送的消息,通过Handler发送的消息会在MessageQueue中等待处理。每个线程中只会有一个MessageQueue对象。

Looper

Looper对象时每个线程中的MessageQueue的管家。调用Looper()的loop()方法后,就会进入一个无限循环中,然后,每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的 handlerMessage()方法中。此外,每个线程也只会有一个Looper对象。在主线程中创建Handler对象时,系统已经创建了Looper对象,所以不用手动创建Looper对象,而在子线程总的Handler对象,需要调用Looper.loop()方法开启消息循环。

image image image image

9.3 AsyncTask 异步任务

为了方便在子线程中对UI进行操作,Android提供了一些好用的工具类,AsyncTask就是其中之一。借助AsyncTask可以十分简单地从子线程切换到主线程,它的原理也是基于异步消息处理机制

在没有AsyncTask之前, 我们用Handler+Thread就可以实现异步任务的
功能需求。AsyncTask是对Handler和Thread的封装, 使用它编码更简洁,更高效。AsyncTask封装了ThreadPool, 比直接使用Thread效率要高

image image

AsyncTask是一个抽象类,因此要使用它必须创建一个类去继承它。在继承AsyncTask时,可以为其指定三个泛型参数,这三个参数如下:

  • Params:在执行AsyncTask时需要传入的参数,用于后台任务中使用。

  • Progress:后台任务执行时,如果需要在界面上显示当前进度,则使用该参数作为进度单位

  • Result:当任务执行完毕后,如果需要对结果进行返回,则使用该参数作为返回值类型。

    class DownLoadTask extends AsyncTask<Void,Integer,Boolean>

AsyncTask的三个泛型参数分别被指定为Void、Integer、Boolean类中,其中,将第一个参数指定为Void类型,表示在执行AsyncTask时,不需要传递参数给后台任务。将第二个参数指定为Integer类型,表示使用整型来作为进度单位。将第三个参数指定为Boolean类型,表示使用布尔类型来返回执行结果。

通常在使用AsyncTask时,需要重写它的四个方法,这四个方法的用法如下所示:

  • onPreExcute():这个方法在后台任务执行之前调用,一般用于界面上初始化操作,例如,显示一个进度条对话框等

  • doInBackground(Params...):这个方法在子线程中执行,用于处理耗时操作,操作一旦完成可以通过return语句来将任务的执行结果返回。如果ASyncTask的第三个参数被指定为Void则可以不用返回执行结果。需要注意的是,这个方法不能进行更新UI操作,如果需要在该方法中更新UI可以手动调用publishProgress(Progress....)方法来完成。

  • onProgressUpdate(Progress):如果在doInBackground(Params...)方法中调用了publishProgress(Progress....),这个方法就会很快被调用,方法找那个携带的参数就是后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数Progress就可以对UI进行相应的更新。

  • onPostExcute(Result):当doInBackground(Params...)方法执行完毕通过return语句返回时,这个方法会很快被调用。在doInBackground(Params...)方法中返回的数据会作为参数传递到该方法中。此时,可以利用返回的参数来进行UI操作,例如,提醒某个任务完成了。

calss DownLoadTask extends AsyncTask<Void,Integer,Boolean>
{
    @override
    protected void onPreExecute()
    {
        //显示进度对话框
        ProgressDialog.show();
    }

    @override
    protected Boolean doInBackgroud(Void....params)
    {
        while(true)
        {
            //doDown()是一个虚构方法,用来返回下载百分比
            int downloadPrecent=doDown();
            //更新下载进度
            publishProgress(downloadPrecent);
            if(downloadPrecent>=100)
            {
                break;
            }
        }
        return true;
    }

    @override
    protected void onProgressUpdate(Integer... values)
    {
        //在这里更新下载进度
        progressDialog.setMessage("Downlaod"+values[0]+"%");
    }

    @override
    protected void onPostExecute(Boolean result)
    {
        //关闭对话框
        progressDialog.dismiss();
        //在这里提示下载结果
        if(result)
        {
            Toasst....success
        }
        else
        {
            Toast...failed
        }
    }
}

要执行DownLoadTask,还需要在UI线程中创建DownTask的实例,并调用DownTask实例的excute()方法,如下:

new DownLoadTask().excute();

9.4 使用HttpURLConnection访问网络

image

Android客户端原生访问网络发送HTTP请求的方式一般有两种:HttpURLConnection和HTTPClient。HttpURLConnection是Java的标准类,HTTPClient是一个开源项目。

9.4.1 HttpURLConnection的基本用法

//在URL的构造方法总传入要访问资源路径
URL url=new URL("http://www.baidu.cn");

HttpURLConnection conn=(HttpURLConnection)url.openConnection();

conn.setRequesetMethod("GET");//设置请求方式

conn.setConnectTimeout(5000);//设置超时时间

InputStream is=conn.getInputStream();//获取服务器返回的输入流

try
{
    //读取流信息 获得服务器返回的数据
}
catch(Exception e)
{
    
}
conn.disconnect();//关闭http连接

需要注意的是,使用HttpURLConnection访问网络时,需要设置超时时间,如果不设置超时时间,在网络异常的情况下,会得不到数据而一直等待,导致程序僵死不往下执行。

9.4.2 HttpURLConnection基本用法案例---网络图片浏览

布局

<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" >

    <ImageView
        android:id="@+id/iv"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1000" />

    <EditText
        android:id="@+id/et_path"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="请输入图片路径"
        android:singleLine="true"
        android:text="http://b.hiphotos.baidu.com/image/w%3D310/sign=a439f5b24510b912bfc1f0fff3fdfcb5/83025aafa40f4bfb92c52c5d014f78f0f73618a5.jpg" />

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="click"
        android:text="浏览" />

</LinearLayout>

在这个布局文件中用到了 android:layout_weight="1000"属性,这里的weight不是权重的意思,而是代表控件渲染的优先级。weight的值越大,表示这个控件渲染的优先级越低

界面交互代码

public class MainActivity extends Activity
{
    protected static final int CHANGE_UI = 1;
    protected static final int ERROR = 2;
    private EditText et_path;
    private ImageView iv;
    // 主线程创建消息处理器
    private Handler handler = new Handler()
    {
        public void handleMessage(android.os.Message msg)
        {
            if (msg.what == CHANGE_UI)
            {
                Bitmap bitmap = (Bitmap) msg.obj;
                iv.setImageBitmap(bitmap);
            }
            else if (msg.what == ERROR)
            {
                Toast.makeText(MainActivity.this, "显示图片错误", 0).show();
            }
        };
    };

    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_path = (EditText) findViewById(R.id.et_path);
        iv = (ImageView) findViewById(R.id.iv);
    }

    public void click(View view)
    {
        final String path = et_path.getText().toString().trim();
        if (TextUtils.isEmpty(path))
        {
            Toast.makeText(this, "图片路径不能为空", 0).show();
        }
        else
        {
            // 子线程请求网络,Android4.0以后访问网络不能放在主线程中
            new Thread()
            {
                public void run()
                {
                    // 连接服务器 get 请求 获取图片.
                    try
                    {
                        URL url = new URL(path); // 创建URL对象
                        // 根据url 发送 http的请求.
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        // 设置请求的方式
                        conn.setRequestMethod("GET");
                        // 设置超时时间
                        conn.setConnectTimeout(5000);
                        // 设置请求头 User-Agent浏览器的版本
                        conn.setRequestProperty("User-Agent",
                                "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; "
                                        + "SV1; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; "
                                        + ".NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; Shuame)");
                        // 得到服务器返回的响应码
                        int code = conn.getResponseCode();
                        // 请求网络成功后返回码是200
                        if (code == 200)
                        {
                            // 获取输入流
                            InputStream is = conn.getInputStream();
                            // 将流转换成Bitmap对象
                            Bitmap bitmap = BitmapFactory.decodeStream(is);
                            // iv.setImageBitmap(bitmap);
                            // TODO: 告诉主线程一个消息:帮我更改界面。内容:bitmap
                            Message msg = new Message();
                            msg.what = CHANGE_UI;
                            msg.obj = bitmap;
                            handler.sendMessage(msg);
                        }
                        else
                        {
                            // 返回码不是200 请求服务器失败
                            Message msg = new Message();
                            msg.what = ERROR;
                            handler.sendMessage(msg);
                        }
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                        Message msg = new Message();
                        msg.what = ERROR;
                        handler.sendMessage(msg);
                    }
                };
            }.start();
        }
    }
}

联网权限

<uses-permission android:name="android.permission.INTERNET">

9.5 使用HttpClient访问网络

HttpClient是Apache的一个开源项目,从一开始就被引入到了Android的API中。HttpClient可以完成和HttpURLConnection一样的效果,但它使用起来更简单。简单来说HttpClient是HttpURLConnection的增强版。

9.5.1 HttpClient的基本用法

具体步骤如下所示:

  • 创建HttpClient对象

  • 指定访问网络的方式,创建一个HttpPost对象或者HTTPGet对象

  • 入股需要发送请求参数,可调用HttpGet、HttpPost都具有的setParams()方法。对于HttpPost对象而言,也可以调用setEntity()方法来设置请求参数。

  • 调用HttpClient对象的execute()方法来访问网络,并获取HTTPResPonse对象。

  • 调用HttpResponse.getEntity()方法获取HttpEntity对象,该对象包装了服务器的响应内容,也就是所请求的数据

HttpClient常用类介绍

常用类名称 功能描述
HttpClient 请求网络的接口
DefaultHttpClient 实现了HttpClient接口的类
HttpGet 使用GET方式请求需要创建该类实例
HttpPost 使用Post方式请求需要创建该类实例
NameValuePair 关联参数的Key、value
BasicNameValuePair 以Key、Value的形式存放参数的类
UrlEncodeFormEntity 将提交给服务器参数进行编码的类
HtttpResponse 封装了服务器返回信息的类(包含头信息)
HttpEntity 封装了服务返回数据类

具体代码如下所示:

//获取到HttpClient对象
HttpClient client=new DefaultHttpClient();
HttpPost httpPost=new HttpPost("http://www.baidu.com");
List<NameValuePair>params=new ArrayList<NameValuePair>();
//创建一个NameValuePair集合,用于添加参数
params.add(new BasicNameValuePair("username","admin"));
//给参数设置编码
UrlEncodeFormEntity entity=new UrlEncodeFormEntity(params,"utf-8");
//设置参数
httpPost.setEntity(entity);
//获取HTTPResponse对象
HttpResponse httpResponse.getStatusLine().getStatusCode();
//获取状态码
int statusCode=httpResponse.getStatuLine().getStatusCode();
if(statusCode==200)
{
    //获取HttpEntity的实例
    HttpEntity HttpEntity=httpResponse。getEntity();
    //设置编码格式
    String response=EntityUtils.toString(httpEntity,"utf-8");
} 

上述代码演示了如何使用HttpClient访问服务器并返回的数据。需要注意的是,使用POST方式设置参数时,需要创建一个NameValuePair的集合来添加参数。在给参数设置编码时,需要与服务器的解码格式保持一致性,否则会出现中文乱码的情况。

9.5.2 案例------网络图片浏览(使用HttpClient)

编写界面交互代码
public class MainActivity extends Activity
{
    protected static final int CHANGE_UI = 1;
    protected static final int ERROR = 2;
    private EditText et_path;
    private ImageView iv;
    // 1. 主线程创建消息处理器
    private Handler handler = new Handler()
    {
        public void handleMessage(android.os.Message msg)
        {
            if (msg.what == CHANGE_UI)
            {
                Bitmap bitmap = (Bitmap) msg.obj;
                iv.setImageBitmap(bitmap);
            }
            else if (msg.what == ERROR)
            {
                Toast.makeText(MainActivity.this, "显示图片错误", 0).show();
            }
        };
    };

    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_path = (EditText) findViewById(R.id.et_path);
        iv = (ImageView) findViewById(R.id.iv);
    }

    public void click(View view)
    {
        final String path = et_path.getText().toString().trim();
        if (TextUtils.isEmpty(path))
        {
            Toast.makeText(this, "图片路径不能为空", 0).show();
        }
        else
        {
            new Thread()
            {
                public void run()
                {
                    // 连接服务器 get 请求 获取图片
                    getImageByClient(path);// 使用HttpClient访问网络
                };
            }.start();
        }
    }

    // 使用HttpClient访问网络
    protected void getImageByClient(String path)
    {
        HttpClient client = new DefaultHttpClient();// 获取HttpClient对象
        HttpGet httpGet = new HttpGet(path); // 用get方式请求网络
        try
        {
            HttpResponse httpResponse = client.execute(httpGet);
            // 获取返回的HttpResponse对象
            if (httpResponse.getStatusLine().getStatusCode() == 200)
            {
                // 检验服务器返回的状态码是否为200
                HttpEntity entity = httpResponse.getEntity();// 拿到HttpEntity对象
                InputStream content = entity.getContent(); // 拿到输入流
                // 拿到bitmap对象
                Bitmap bitmap = BitmapFactory.decodeStream(content);
                // TODO 通知主线程更改Ui界面
                Message message = new Message();
                message.what = CHANGE_UI;
                message.obj = bitmap;
                handler.sendMessage(message);
            }
            else
            {
                // 状态码不为200 访问服务器不成功
                Message message = new Message();
                message.what = ERROR;
                handler.sendMessage(message);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
            Message message = new Message();
            message.what = ERROR;
            handler.sendMessage(message);
        }
    }
}

上述代码的重点在getImageClinet()方法里,本段代码采用的是HttpClient的Get方式请求获取网络图片资源,访问服务器成功返回200的状态码,此时需要获取服务器返回的图片数据。通过调用HTTPResponse的getEntity()方法获得HttpEntity对象,然后调用HttpEntity的getContent()方法得到输入流,最后通过BitmapFactory生成Bitmap对象,从而将服务器返回的信息转换成图片。


9.6 数据提交方式

GET方式和POST方式提交数据,Http/1.1协议中共定义了8种方法来表明Request-URL指定的资源的不同操作方式。其中最常用的两种请求方式时GET和POST,

9.6.1 GET方式与POST方式的区别

  • GET方式时以实体的方式得到由请求URL所指向的资源信息,它向服务器提交的参数跟在请求URL后面。使用GET方式访问网络URL的长度时有限制的。HTTP协议规定GET方式请求URL的长度不超过4k。但是IE浏览器GET方式请求URL的长度不能超过1k,为了兼容,因此GET方式请求URL的长度要小于1k

  • POST方式用来向目的服务器发出请求,要求它接受被附在请求后的实体。它向服务器提交的参数在请求后的实体中,它提交的参数是浏览器同流的方式直接写给服务器的,此外,POST方式对URL的长度时没有限制的。

9.6.2 GET方式提交数据

//将用户名和密码拼装在指定资源路径后面,并给用户名和密码进行编码
String path="http://192.168.1.100:8080/web/LoginServlet?username="+URLEncoder.encoder("Hashub")+"&password="+URLEncoder.encode("123");

URL url=new URL(path);//创建URL对象

HttpURLConnection conn=(HttpURLConnection)url.openConnection();

conn.setRequestMethod("GET");//设置请求方式

conn.setConnectTimeout(5000);//设置超时时间

int responseCode=conn.getResponseCode();//获取状态码

if(responseCode==200)
{
    InputStream is=conn.getInputStream();
    try
    {
        
    }
    catch()
    {
        //读取输入流里面的信息
    }
}

9.6.3 POST方式提交数据

使用POST方式请求网络,请求参数跟在请求实体中,用户不能在浏览器中看到向服务器的请求参数,因此POST方式比GET方式相对安全。

//使用HttpURLConnection
String path="http://192.168.1.100:8080/web/LoginSrvlet";

URL url=new URL(path);

HttpURLConnection conn=(HttpURLConnection)url.openConnection();

conn.setRequestMethod("POST");//设置请求方式

conn.setConnectTimeout(5000);//设置超时时间

//准备数据并给参数进行编码
String data="username="+URLEncoder.encoder("Hashub")+"&password="+URLEncoder.encode("123");

//设置请求头---设置提交数据的长度
conn.setRequestProperty("Content-Length",data.length()+"");

//post方式,实际上是浏览器把数据写给了服务器
conn.setDoOutput(true);//设置允许向外写数据

OutputStream os=conn.getOutputStream();//利用输出流往服务器写数据

os.write(data.getBytes());//将数据写给服务器

int code=conn.getResponseCode();//获取状态码

if(code==200)//请求成功
{
    InputSTream is=ocnn.getInputStream();
    //读取服务器返回的信息
}

从上述代码可以看出,使用HttpURLConnection的POST方式提交数据时,是以流的方式直接将参数写到服务器上的,需要设置数据的提交方式和数据的长度。

在实际开发中,手机端与服务器进行交互的过程中,避免不了要提交大服务器,这时就会出现中文乱码的情况。无论是GET方式时POST方式,提交参数时都要给参数进行编码,需要注意的是,编码方式必须与服务器解码方式统一,同样在获取服务器返回的中文字符时,也需要用指定格式进行解码。


9.6.4 案例------提交数据到服务器

布局

<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" >

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"
        android:text="张三" />

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入密码"
        android:inputType="textPassword" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click1"
        android:text="Get方式登录" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click2"
        android:text="POST方式登录" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click3"
        android:text="Client get方式登录" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click4"
        android:text="Client post方式登录" />

</LinearLayout>

界面交互代码

public class MainActivity extends Activity
{
    private EditText et_username;
    private EditText et_password;

    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化控件
        et_password = (EditText) findViewById(R.id.et_password);
        et_username = (EditText) findViewById(R.id.et_username);
    }

    // HttpURLConnection GET方式
    public void click1(View view)
    {
        // 拿到用户输入的用户名
        final String username = et_username.getText().toString().trim();
        // 拿到密码
        final String password = et_password.getText().toString().trim();
        new Thread()
        {
            public void run()
            {
                // 调用LoginService里面的方法访问服务器 并拿到服务器返回的信息
                final String result = LoginService.loginByGet(username, password);
                if (result != null)
                {
                    // UI线程更改界面
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Toast.makeText(MainActivity.this, result, 0).show();
                        }
                    });
                }
                else
                {
                    // 请求失败 UI线程弹出toast
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Toast.makeText(MainActivity.this, "请求失败...", 0).show();
                        }
                    });
                }
            };
        }.start();
    }

    // HttpURLConnection POST方式
    public void click2(View view)
    {
        // 首先获取界面用户输入的用户名和密码
        final String username = et_username.getText().toString().trim();
        final String password = et_password.getText().toString().trim();
        new Thread()
        {// 开启子线程访问网络
            public void run()
            {
                // 调用LoginService里面的方法访问网络
                final String result = LoginService.loginByPost(username, password);
                if (result != null)
                {
                    // ui线程更改界面
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Toast.makeText(MainActivity.this, result, 0).show();
                        }
                    });
                }
                else
                {
                    // 请求失败,使用UI线程更改UI界面
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Toast.makeText(MainActivity.this, "请求失败...", 0).show();
                        }
                    });
                }
            };
        }.start();
    }

    // HttpClient GET方式
    public void click3(View view)
    {
        // 拿到输入的用户名和密码
        final String username = et_username.getText().toString().trim();
        final String password = et_password.getText().toString().trim();
        new Thread()
        {// 开启子线程访问网络
            public void run()
            {
                // 调用LoginService里面的方法请求网络获取数据
                final String result = LoginService.loginByClientGet(username, password);
                if (result != null)
                {
                    // 使用UI线程弹出toast
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Toast.makeText(MainActivity.this, result, 0).show();
                        }
                    });
                }
                else
                {// 请求失败 使用UI线程弹出toast
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Toast.makeText(MainActivity.this, "请求失败...", 0).show();
                        }
                    });
                }
            };
        }.start();
    }

    // HttpClient POST方式
    public void click4(View view)
    {
        // 拿到输入的用户名和密码
        final String username = et_username.getText().toString().trim();
        final String password = et_password.getText().toString().trim();
        new Thread()
        {
            public void run()
            {
                // 使用工具类LoginService里面的方法访问网络 并拿到从服务器返回的信息
                final String result = LoginService.loginByClientPost(username, password);
                if (result != null)
                {
                    // ui线程更改界面
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            // 弹出toast
                            Toast.makeText(MainActivity.this, result, 0).show();
                        }
                    });
                }
                else
                {
                    // 请求失败 弹出toast
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Toast.makeText(MainActivity.this, "请求失败...", 0).show();
                        }
                    });
                }
            };
        }.start();
    }
}

将访问网络的代码封装成工具类LoginService

public class LoginService
{
    // 使用HttpURLConnection GET方式提交数据
    public static String loginByGet(String username, String password)
    {
        try
        {
            // 拼装URL 注意为了防止乱码 这里需要将参数进行编码
            String path = "http://172.16.26.59:8080/Web/LoginServlet?username=" + URLEncoder.encode(username, "UTF-8")
                    + "&password=" + URLEncoder.encode(password);
            // 创建URL实例
            URL url = new URL(path);
            // 获取HttpURLConnection对象
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000); // 设置超时时间
            conn.setRequestMethod("GET"); // 设置访问方式
            int code = conn.getResponseCode(); // 拿到返回的状态码
            if (code == 200)
            { // 请求成功
                InputStream is = conn.getInputStream();
                String text = StreamTools.readInputStream(is);
                return text;
            }
            else
            {
                return null;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    // 使用HttpURLConnection POST方式提交数据
    public static String loginByPost(String username, String password)
    {
        try
        {
            // 要访问的资源路径
            String path = "http://172.16.26.59:8080/Web/LoginServlet";
            // 创建URL的实例
            URL url = new URL(path);
            // 获取HttpURLConnection对象
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 设置超时时间
            conn.setConnectTimeout(5000);
            // 指定请求方式
            conn.setRequestMethod("POST");
            // 准备数据 将参数编码
            String data = "username=" + URLEncoder.encode(username) + "&password=" + URLEncoder.encode(password);
            // 设置请求头
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setRequestProperty("Content-Length", data.length() + "");
            // 将数据写给服务器
            conn.setDoOutput(true);
            // 得到输出流
            OutputStream os = conn.getOutputStream();
            os.write(data.getBytes()); // 将数据写入输出流中
            int code = conn.getResponseCode(); // 那到服务器返回的状态码
            if (code == 200)
            {
                // 得到服务器返回的输入流
                InputStream is = conn.getInputStream();
                // 将输入流转换成字符串
                String text = StreamTools.readInputStream(is);
                return text;
            }
            else
            {
                return null;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    // 采用httpclient get提交数据
    public static String loginByClientGet(String username, String password)
    {
        try
        {
            // 1 、创建HttpClient对象
            HttpClient client = new DefaultHttpClient();
            // 2、拼装路径,注意将参数编码
            String path = "http://172.16.26.59:8080/Web/LoginServlet?username=" + URLEncoder.encode(username)
                    + "&password=" + URLEncoder.encode(password);
            // 3、GET方式请求
            HttpGet httpGet = new HttpGet(path);
            // 4、拿到服务器返回的HttpResponse对象
            HttpResponse response = client.execute(httpGet);
            // 5、拿到状态码
            int code = response.getStatusLine().getStatusCode();
            if (code == 200)
            {
                // 获取输入流
                InputStream is = response.getEntity().getContent();
                // 将输入流转换成字符串
                String text = StreamTools.readInputStream(is);
                return text;
            }
            else
            {
                return null;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    // 采用httpclient post提交数据
    public static String loginByClientPost(String username, String password)
    {
        try
        {
            // 1、获取HttpClient对象
            HttpClient client = new DefaultHttpClient();
            // 2、指定访问地址
            String path = "http://172.16.26.59:8080/Web/LoginServlet";
            // 3、POST方式请求网络
            HttpPost httpPost = new HttpPost(path);
            // 4、指定要提交的数据实体
            List<NameValuePair> parameters = new ArrayList<NameValuePair>();
            parameters.add(new BasicNameValuePair("username", username));
            parameters.add(new BasicNameValuePair("password", password));
            httpPost.setEntity(new UrlEncodedFormEntity(parameters, "UTF-8"));
            // 5、请求服务器并拿到服务器返回的信息
            HttpResponse response = client.execute(httpPost);
            int code = response.getStatusLine().getStatusCode(); // 拿到状态码
            if (code == 200)
            { // 访问成功
                InputStream is = response.getEntity().getContent();
                // 将输入流转换成字符串
                String text = StreamTools.readInputStream(is);
                return text;
            }
            else
            {
                return null;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }
}

StreamTools,将输入流转换成字符串的工具类

public class StreamTools
{
    // 把输入流的内容 转化成 字符串
    public static String readInputStream(InputStream is)
    {
        try
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1)
            {
                baos.write(buffer, 0, len);
            }
            is.close();
            baos.close();
            byte[] result = baos.toByteArray();
            // 试着解析 result 里面的字符串.
            String temp = new String(result);
            return temp;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return "获取失败";
        }
    }
}

Web工程实例

public class LoginServlet extends HttpServlet
{
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public LoginServlet()
    {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        String username = request.getParameter("username");// iso 8859 -1
        String password = request.getParameter("password");
        System.out.println("username:" + new String(username.getBytes("iso-8859-1"), "utf-8"));
        System.out.println("password:" + password);

        if ("zhangsan".equals(username) && "123".equals(password))
        {
            response.getOutputStream().write("登录成功".getBytes("utf-8"));
        }
        else
        {
            response.getOutputStream().write("登录失败".getBytes("utf-8"));
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        System.out.println("post");
        doGet(req, resp);
    }
}

上述代码的主要功能是接收来自手机端的请求、获取到手机端的参数、校验手机端提交的数据、响应手机端请求并返回相应的数据。当手机端传递过来的用户名密码匹配时,服务端就会返回“登录成功”的信息,否则返回"登录失败"的信息,需要注意的是,要在web.xml文件注册这个Servlet,具体代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name></display-name> 
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
 <servlet> 
 <servlet-name>LoginServlet</servlet-name>
 <servlet-class>cn.itcast.web.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name> 
 <url-pattern>/LoginServlet</url-pattern>
</servlet-mapping>
</web-app>

9.7 AsyncHttpClient

AsyncHttpClient是对HttpClient的再次包装。AsyncHttpClient的特点有,发送异步Http请求、HTTP请求发生在UI线程之外、内部采用了线程池来处理并发请求。而且使用起来比HttpClient更加简便。

下载导入jar包即可: http://github.com/loopj/android-ssync-http

9.7.1 AsyncHttpClient的用法

首先创建AsyncHttpClient的实例,然后设置参数,接着通过AsyncHttpClient的实例对象访问网络,如果访问成功会回调AsyncHttpResponseHandler接口中的OnSucess方法,失败则会回调OnFailure方法。

//创建AsyncHttpClient的实例
AsyncHttpClient client =new AsyncHttpClient();

//拼装URL,注意要将参数编码
String path="http://192.168.1.100:8080/web/LoginServlet?username="+URLEncoder.encoder("Hashub")+"&password="+URLEncoder.encode("1234");

//GET 方式请求网络
client.get(path,new AsyncHttpResponseHandler()
{
    //访问网络成功
    public void onSuccess(String content)
    {
        super.onSuccess(content);
        Toast....
    }
    
    //访问网络失败
    public void onFailure(Throwable error,String content)
    {
        super.onFailure(error,content);
        Toast。。。。
    }
});

//POST 方式访问网络
AsyncHttpClient client=new AsyncHttpClient();

//访问地址
String url="http://192.168.1.100:8080/web/LoginServlet";

//用于添加参数的类
RequestParams params=new RequestParams();

//添加参数
params.put("name","Hashub");
params.put("pass","113232");

//访问网络
client.post(url,params,new AsyncHttpResponseHandler()
{
    //访问成功
    public void onSuccess(int statusCode,Handler[]headers,byte[]responseBody)
    {
        super.onSuccess(statusCode,headers,responseBody);
        Toast....
    }
    
    //访问网络失败
    public void onFailure(Throwable error,String content)
    {
        super.onFailure(error,content);
        Toast.......
    }

});

9.8 多线程下载

使用多线程下载资源,先要获取到服务器资源文件的大小,然后在本地创建一个大小与服务器资源一样大的文件,接着在客户端开启若干个线程去下载服务器的资源。需要注意的是,每个线程必须要下载对应的模块,然后将每个线程下载的模块按顺序组装成资源文件。

image

从图中可以看出,每个线程下载的区域就是总大小/线程个数。但不能保证每个文件都可以完全平均分配资源,因此最后一个线程需要下载到文件的末尾。需要注意的是,使用多线程下载时,需要在请求头中设置Range字段获取到指定位置的数据,

9.8.1 多线程下载案例

public class MainActivity extends Activity
{
    // 服务器资源地址
    private static final String path = "http://172.16.26.58:8080/EditPlus.exe";
    private TextView mFileTV; // 用于展示服务器资源文件的大小
    private TextView mThread1TV; // 用于显示thread需要下载的文件长度
    private TextView mThread3CompleteTV; // thread下载完成时显示
    protected static int threadCount; // 线程个数
    // 用于更新UI界面的Handler
    private Handler handler = new Handler()
    {
        public void handleMessage(android.os.Message msg)
        {

            switch (msg.what)
            {
            case 100: // 服务器资源文件的大小
                mFileTV.setText("服务器资源文件大小为:" + (Long) msg.obj);
                break;
            case 101: // 计算每个线程需要下载多少
                String string = mThread1TV.getText().toString();
                mThread1TV.setText(string + (String) msg.obj);
                break;
            case 102:// 查看那个线程下载的最快
                String string1 = mThread3CompleteTV.getText().toString();
                mThread3CompleteTV.setText(string1 + (String) msg.obj);
                break;
            case 300:
                Toast.makeText(MainActivity.this, "获取不到服务器文件", 0).show();
                break;
            }
        };
    };

    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    // 初始化控件
    private void initView()
    {
        mFileTV = (TextView) findViewById(R.id.file);
        mThread1TV = (TextView) findViewById(R.id.thread1);
        mThread3CompleteTV = (TextView) findViewById(R.id.thread3_complete);
    }

    // Button点击事件触发的方法
    public void downLoad(View view)
    {
        // 1本地创建一个文件大小与服务器资源大小一样
        new Thread()
        {
            public void run()
            {
                try
                {
                    URL url = new URL(path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(3000);
                    conn.setReadTimeout(2000);
                    // 获取服务器资源文件的大小
                    long contentLength = conn.getContentLength();
                    if (contentLength <= 0)
                    {
                        Message msg = new Message();
                        msg.what = 300;
                        handler.sendMessage(msg);
                        return;
                    }
                    // 使用Handler发送消息更改界面
                    Message msg = new Message();
                    msg.what = 100;
                    msg.obj = new Long(contentLength);
                    handler.sendMessage(msg);
                    // 本地创建一个随机文件并制定类型
                    RandomAccessFile raf = new RandomAccessFile("/sdcard/temp.exe", "rwd");
                    // 设置本地文件的大小
                    raf.setLength(contentLength);
                    // 线程的数量
                    threadCount = 3;
                    // 每个线程下载的区块的大小
                    long blocksize = contentLength / threadCount;
                    // 计算出来每个线程 下载的开始和结束的位置.
                    for (int threadId = 1; threadId <= threadCount; threadId++)
                    {
                        long startPos = (threadId - 1) * blocksize;
                        long endPos = threadId * blocksize - 1;
                        if (threadId == threadCount)
                        {
                            // 最后一个线程
                            endPos = contentLength;
                        }
                        Message message = new Message();
                        message.what = 101;
                        message.obj = "线程" + threadId + "需下载" + startPos + "-" + endPos + "\n";
                        handler.sendMessage(message);
                        // 开起线程开始下载文件
                        new DownLoadThread(startPos, endPos, threadId, path).start();
                    }
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            };
        }.start();
    }

    // 自定一个线程用于下载文件
    class DownLoadThread extends Thread
    {
        private long startPos;
        private long endPos;
        private long threadId;
        private String path;

        public DownLoadThread(long startPos, long endPos, long threadId, String path)
        {
            super();
            this.startPos = startPos;
            this.endPos = endPos;
            this.threadId = threadId;
            this.path = path;
        }

        public void run()
        {
            try
            {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET"); // 设置请求方法
                conn.setConnectTimeout(5000); // 设置超时时间
                // 请求部分数据 请求成功返回206
                conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
                InputStream is = conn.getInputStream();
                RandomAccessFile raf = new RandomAccessFile("/sdcard/temp.exe", "rwd");
                // 重新指定某个线程保存文件的开始位置 需与服务器下载的位置一致
                raf.seek(startPos);
                // 将数据写到raf中
                int len = 0;
                byte[] buffer = new byte[1024];
                while ((len = is.read(buffer)) != -1)
                {
                    raf.write(buffer, 0, len);
                }
                is.close();
                raf.close();
                // 使用handler给主线程发送消息
                Message msg = new Message();
                msg.what = 102;
                msg.obj = new String("线程" + threadId + "下载完成" + "\n");
                handler.sendMessage(msg);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}

相关文章

  • Chapter 09. 消息机制与异步任务&网络编程

    阅读原文 消息机制与异步任务&网络编程 9.1 Handler消息机制与异步任务 在Android中,只有在UIT...

  • Dart异步任务与消息循环机制

    Dart异步任务与消息循环机制 Future , async/await 表示异步任务 一个Dart应用有一个消息...

  • JavaScript 异步编程

    同步模式与异步模式 时间循环与消息队列 异步编程的几种方式 Primise异步方案 宏任务 /微任务队列 Ge...

  • Android的消息机制与异步任务

    消息机制的引入 在Android中,只有在UIThread中才能直接更新用户界面 在Android中,长时间的工作...

  • Android中UI的更新方式

    使用Handler消息传递机制; 使用AsyncTask异步任务; 使用runOnUiThread(action)...

  • 同步、异步、阻塞与非阻塞

    同步与异步 首先来解释同步和异步的概念,这两个概念与消息的通知机制有关。也就是同步与异步主要是从消息通知机制角度来...

  • AsyncTask 源码分析

    AsyncTask 的使用 Android 提供 AsyncTask 处理异步任务,基于异步消息处理机制,本质上是...

  • java多线程(核心篇)第九章

    第九章 Java异步编程 9.1 同步计算与异步计算 以异步方式执行的任务,称之为异步任务,其任务的发起与任务的执...

  • IO模型

    同步,异步,阻塞和非阻塞的概念与区别 同步和异步从消息通知机制角度来说的。 两个任务 同步一直等,异步不等 阻塞和...

  • 网络编程基本概念

    网络编程有一些基本概念需要理解掌握,比如同步、异步、阻塞非阻塞。 1. 同步vs异步 同步与异步主要是从消息通知机...

网友评论

      本文标题:Chapter 09. 消息机制与异步任务&网络编程

      本文链接:https://www.haomeiwen.com/subject/acfnaftx.html