消息机制与异步任务&网络编程
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();
}
}
}
}










网友评论