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後要動態申請,所以加了申請權限的操作,這樣就大功告成了。

效果圖

小夜