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后要动态申请,所以加了申请权限的操作,这样就大功告成了。
效果图