Android网络框架OKhttp3学习笔记
OKhttp3是一个非常强大的Android网络框架,它是由Square公司开发并开源的,很大Android开发者都会使用到,所以我也要来学学。
服务器
为了方便测试,我们需要一个后台服务器的的应用,下面是一个Java Web的Servlet,它的功能是接收客户端发来的登录数据,判断密码是否正确,并返回结果(JSON格式)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
@WebServlet(name = "MyServlet",urlPatterns = {"/MyServlet"})
public class MyServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取表单数据
String number = request.getParameter("number");
String pwd = request.getParameter("pwd");
String body;
//判断密码和账号有没有正确
if (number.equals("12345") && pwd.equals("12345")){
body = "{\"result\":\"success\",\"file\":\"/file/123.jpg\"}";
}else {
body = "{\"result\":\"faile\"}";
}
//打印结果
PrintWriter writer = response.getWriter();
writer.write(body);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
客户端
使用OKhttp3要添加依赖库,下面的语句会加入两个库,一个是基础包Okio,另一个就是OKhttp
compile 'com.squareup.okhttp3:okhttp:3.8.1'
需要创建两个Activity,一个是登录界面,一个是登录结果界面
然后把 activity_main.xml
修改成如下,做出一个登录界面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.dell.testokhttp3.MainActivity">
<EditText
android:id="@+id/edit_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入账号" />
<EditText
android:id="@+id/edit_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:onClick="getRequest"
android:text="get登录" />
<Button
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:onClick="postRequest"
android:text="post登录" />
</LinearLayout>
</LinearLayout>
界面图
之后把 activity_main2.xml
修改成如下,做出一个登录成功后的界面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.dell.testokhttp3.Main2Activity">
<Button
android:text="显示图片"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/showImage"
android:onClick="showImage" />
<Button
android:text="保存图片"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/saveImage"
android:onClick="saveImage" />
<ImageView
android:id="@+id/imageview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
界面图
在MainActivity修改代码
一些初始化操作
//网址主连接
private String url = "http://10.0.2.2:8080";
private EditText number;
private EditText pwd;
number = (EditText) findViewById(R.id.edit_number);
pwd = (EditText) findViewById(R.id.edit_pwd);
我共使用了两种登录方式,分别是GET和POST这两种提交方式,
GET方式,一般登录不会用这种方式,GET是把数据放在地址上的,很容易就被其他人看到,我这只是测试而已
//使用GET方法
public void get() {
final String numberStr = number.getText().toString().trim();
final String pwdStr = pwd.getText().toString().trim();
//连接网络要使用线程
new Thread(new Runnable() {
@Override
public void run() {
//GET方法要在路径上写好数据
Request request = new Request.Builder()
.url(url + "/MyServlet?number=" + numberStr + "&pwd=" + pwdStr)
.build();
OkHttpClient client = new OkHttpClient.Builder()
//连接超时
.connectTimeout(10, TimeUnit.SECONDS)
//写入超时
.writeTimeout(20, TimeUnit.SECONDS)
//读取超时
.readTimeout(20, TimeUnit.SECONDS)
.build();
Log.d("MainActivity","GET路径为:"+request.url());
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//失败做的一些处理
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//获得返回数据
String res = response.body().string();
Log.d("MainActivity", "GET方法返回的数据为:" + res);
parseJSON(res);
}
});
}
}).start();
}
POST方式,这种方式用处比较多,比如上传文件也是用到POST方式
//使用POST方法
public void post() {
final String numberStr = number.getText().toString().trim();
final String pwdStr = pwd.getText().toString().trim();
//连接网络要使用线程
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient.Builder()
//连接超时
.connectTimeout(10, TimeUnit.SECONDS)
//写入超时
.writeTimeout(20, TimeUnit.SECONDS)
//读取超时
.readTimeout(20, TimeUnit.SECONDS)
.build();
//打包表单数据
FormBody.Builder formBodyBuild = new FormBody.Builder();
formBodyBuild.add("number", numberStr);
formBodyBuild.add("pwd", pwdStr);
//设置请求头
Request request = new Request.Builder()
.url(url+"/MyServlet")
//上传文件的写法
/*.post(RequestBody.create(MediaType.parse("text/x-markdown; charset=utf-8"),
new File("/mnt/sdcard/a.png")))*/
.post(formBodyBuild.build())
.build();
Log.d("MainActivity","POST路径为:"+request.url());
Call call = client.newCall(request);
request.url();
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//失败做的一些处理,比如连接失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//获得返回数据
String res = response.body().string();
Log.d("MainActivity", "POST方法返回的数据为:" + res);
parseJSON(res);
}
});
}
}).start();
}
因为返回的数据是json格式,创建一个用于解析json的方法
//解析JSON数据
public void parseJSON(String jsonStr) {
try {
JSONObject jsonObject = new JSONObject(jsonStr);
String resulet = jsonObject.getString("result");
//判断是否成功
if (resulet.equals("success")) {
String fileUrl = jsonObject.getString("file");
final String imageUrl = url + fileUrl;
//操作UI不能在子线程上操作,要在UI线程上操作
runOnUiThread(new Runnable() {
@Override
public void run() {
//登录成功,跳转到另一个Activity
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
intent.putExtra("imageUrl", imageUrl);
startActivity(intent);
Toast.makeText(getApplicationContext(), "登录成功", Toast.LENGTH_SHORT).show();
}
});
} else {
//登录失败
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "账号或密码错误", Toast.LENGTH_SHORT).show();
}
});
}
} catch (JSONException e) {
e.printStackTrace();
}
}
给那两按钮添加点击事件
public void getRequest(View view) {
get();
}
public void postRequest(View view) {
post();
}
到这里其他就可以进行第一次运行了,因为要联网,还有之后保存图片到SD卡,这都有权限,所以要加上下面的权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
可以运行看看了,首先使用GET的提交方式登录
然后是POST提交方式
运行没有问题后,接下来是编写登录成功后的功能了
接收上一个Activity传来的图片的网址
private String url;
//获取上一个Activity传来的图片路径
Intent intent = getIntent();
url = intent.getStringExtra("imageUrl");
一些界面控件的初始化
private ImageView imageView;
imageView = (ImageView) findViewById(R.id.imageview);
显示网络图片的操作
private void showImage(final String ImageUrl) {
//连接网络要使用线程
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder()
//使用缓存
.cache(new Cache(getExternalCacheDir().getAbsoluteFile(), 1024 * 1024 * 2))
.build();
Request request = new Request.Builder()
.url(ImageUrl)
.build();
Log.d("Main2Activity","图片路径为:"+request.url());
Response response = client.newCall(request).execute();
//获取输入流
InputStream inputStream = response.body().byteStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
//使用Hander线程修改UI
Message msg = new Message();
msg.what = 1;
msg.obj = bitmap;
handler.sendMessage(msg);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
还有一个保存网络图片到本地的SD卡上
public void saveImage(final String imageUrl) {
//连接网络要使用线程
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder()
//使用缓存
.cache(new Cache(getExternalCacheDir().getAbsoluteFile(), 1024 * 1024 * 2))
.build();
Request request = new Request.Builder()
.url(imageUrl)
.build();
Log.d("Main2Activity","图片路径为:"+request.url());
Response response = client.newCall(request).execute();
//获取字节数据
byte[] bytes = response.body().bytes();
//获取文件名
String[] urls = url.split("/");
String filename = urls[urls.length - 1];
//保存文件
File file = new File(Environment.getExternalStorageDirectory(), filename);
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytes);
fos.flush();
fos.close();
//弹出Toast
Message msg = new Message();
msg.what = 2;
msg.obj = "保存成功";
handler.sendMessage(msg);
} catch (IOException e) {
e.printStackTrace();
Log.e("Main2Activity", "图片保存失败");
Message msg = new Message();
msg.what = 2;
msg.obj = "图片保存失败";
handler.sendMessage(msg);
}
}
}).start();
}
在上面两种方法中都可以看到我使用了Handle更新UI,下面就是Handle的实现
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
//设置图片
imageView.setImageBitmap((Bitmap) msg.obj);
break;
case 2:
//一些提示
Toast.makeText(getApplicationContext(), (String) msg.obj, Toast.LENGTH_SHORT).show();
break;
}
}
};
最后就是把刚才的操作添加到按钮的点击事件上,因为作者是在Android6.0上测试的,而读写SD卡的权限在Android6.0后要动态申请,所以加了申请权限的操作,这样就大功告成了。
效果图