RESTful

RESTful:表现层状态转换。大意为资源以某种表现形式在互联网之间传递,而表现形式则是JSON、JEPG、XML等格式,状态转换则通过HTTP协议实现。这是一种互联网转件架构风格。

为了软件能够找到资源的位置,需要定义URI(Uniform Resource Identifier)标准资源识别符。URL就是一种具体的URI。

JSON

JSON是现在常用的数据传输格式。Android处理JSON可以使用JSONObject或者GSON。

JSONObject

来自Java的JSON对象类。

创建:既可以先new一个空的JSONObject对象再手动添加内容,也支持从JSON格式的字符串中自动转换为一个JSON对象。下面是添加属性的例子:

JSONObject jsonObject = new JSONObject();

// JSON中,对象属性的值可以是另一个对象
JSONObject tempJson = new JSONObject();
tempJson.put("min", 11.34);
tempJson.put("max", 19.01);
jsonObject.put("temp", tempJson);

// 对象属性的值也可以是string、int、boolean
jsonObject.put("success", true);

// 或者是一个数组
JSONArray jsonArray = new JSONArray();
jsonArray.put("Adam");
jsonArray.put("Bob");
jsonArray.put("John");
jsonObject.put("notification_user_id", jsonArray);

//通过toString的方法获得JSON格式的字符串
Log.d("JsonDemo", jsonObject.toString());

另外,一个JSON的单元也可以是数组即JSONArray。

下面是从字符串读取JSON对象以及使用JSON对象的例子:

        String s = "{\"temp\":{\"min\":11.34,\"max\":19.01},\"success\":true,\"notification_user_id\":[\"Adam\",\"Bob\",\"John\"]}";
        try {
            JSONObject jsonObject = new JSONObject(s);
            // 获取JSON对象的一个键对应的值
            JSONArray notificationUserId = jsonObject.getJSONArray("notification_user_id");
            /* optBoolean和getBoolean的区别是:
             * optBoolean方法在没有找到这个键时可以返回默认值(即第二个参数)
             * getBoolean方法如果没有找到键,将抛出异常
             * 同理,getString和optString等也是这个区别
             */
            boolean success = jsonObject.optBoolean("unexist",true);
            boolean unexist = jsonObject.getBoolean("unexist");
        } catch (JSONException e) {
            Log.d("JsonDemo", "crash:"+e.getMessage());
            e.printStackTrace();
        }

GSON

Google开发的更加好用的JSON套件,需要引入implementation 'com.google.code.gson:gson:2.8.6’依赖来使用。

GSON在使用之前可以事先创建好POJO简单对象,然后直接用GSON将一个对象解析成JSON格式的字符串或者从一个JSON格式的字符串创建对象,非常方便。

定义便于GSON使用的简单对象类:

package com.byted.chapter5;

import com.google.gson.annotations.SerializedName;

import java.util.List;

public class People {
    // 这里的注解便是对应JSON的键
    @SerializedName("age")
    public int age;
    @SerializedName("name")
    public String firstName;
    @SerializedName("friends")
    public List<String> friends;
}

将对象或数组转化为JSON字符串:

public static void generateGsonString() {
    Gson gson = new Gson();
    People people = new People();
    people.age = 10;
    people.firstName = "sander";
    ArrayList<String> friends = new ArrayList<>();
    friends.add("sss");
    friends.add("ddd");
    friends.add("nnn");
    people.friends=friends;
    // 直接使用toJson方法即可,非常方便,一步到位
    String s = gson.toJson(people);

    People[] p=new People[1];
    p[0]=people;
    String str=gson.toJson(p);
}

将JSON字符串读取转化为数据对象:

public static void parseGsonString(){
    Gson gson = new Gson();
    
    String sp= "    {\n" +
                    "        \"name\": \"sander\",\n" +
                    "        \"age\": 11\n" +
                    "    }" ;
    // 直接传入JSON字符串和目标的类即可生成对象
    People people = gson.fromJson(sp, People.class);

    String s="[\n" +
            "    {\n" +
            "        \"name\": \"sander\",\n" +
            "        \"age\": 11\n" +
            "    },\n" +
            "    {\n" +
            "        \"name\": \"sander\",\n" +
            "        \"age\": 12\n" +
            "    },\n" +
            "    {\n" +
            "        \"name\": \"sander\",\n" +
            "        \"age\": 13\n" +
            "    },\n" +
            "    {\n" +
            "        \"name\": \"sander\",\n" +
            "        \"age\": 14\n" +
            "    },\n" +
            "    {\n" +
            "        \"name\": \"sander\",\n" +
            "        \"age\": 15\n" +
            "    }\n" +
            "]";
    // 对于泛型的List要用getType这种方式获得类的信息
    List<People> peoples = gson.fromJson(s, new TypeToken<List<People>>(){}.getType());
    // 普通的数组类型可以直接读
    People[] peoples2 = gson.fromJson(s, People[].class);
}

Retrofit

Retrofit是一个对HTTP请求框架的封装,使用之前首先添加依赖:implementation "com.squareup.retrofit2:retrofit:2.8.1"

然后需要在src/main/AndroidManifest.xml中添加网络权限:

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

在使用的时候,首先需要创建一个描述网络请求的接口,采用注解的方式指明URL等信息,使用注解+参数传递的方式在POST请求的请求体中加入信息。注意这里的URL是不完整的,省略了Base部分。举例如下:

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Part;

public interface ApiService {
    // 方法:GET
    // https://wanandroid.com/wxarticle/chapters/json
    @GET("wxarticle/chapters/json")
    Call<ArticleResponse> getArticles();


    // https://www.wanandroid.com/user/register
    //方法:POST
    // username,password,repassword
    // @FormUrlEncoded表示将参数中会通过@Field的方式指明键和值
    @FormUrlEncoded
    @POST("user/register")
    Call<UserResponse> register(@Field("username") String username,
                                @Field("password") String password,
                                @Field("repassword") String repassword);
}

在需要进行网络请求的地方,需要实例化Retrofit对象,在这里指明URL的base部分,然后创建API实例。之后,一般建议使用异步请求的方式完成网络连接。注意返回值是泛型的Response类型,具体类型可以自定义,可以使用.body()方法获得内部真正需要的数据对象。使用例如:

private void getData() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://wanandroid.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    // 用刚才创建的接口进行配置,创建ApiService实例
    ApiService apiService = retrofit.create(ApiService.class);
    // 采用异步请求的方式,完成后回调方法
    apiService.getArticles().enqueue(new Callback<ArticleResponse>() {
        //请求成功时调用。这里的ArticleResponse是自定义的
        @Override
        public void onResponse(Call<ArticleResponse> call, Response<ArticleResponse> response) {
            if (response.body() != null) {
                List<ArticleResponse.Article> articles = response.body().articles;
                Log.d("retrofit", articles.toString());
                if (articles.size() != 0) {
                    mAdapter.setData(response.body().articles);
                    mAdapter.notifyDataSetChanged();
                }
            }
        }

        //请求失败时调用
        @Override
        public void onFailure(Call<ArticleResponse> call, Throwable t) {
            Log.d("retrofit", t.getMessage());
        }
    });

}

当然,也可以采用同步的方式,不过可能会造成卡顿等问题。

Response<ArticleResponse> = apiService.getArticles().execute();
AriticleResponse article = response == null ? null : response.body();

本节示例工程

https://github.com/jingjiecb/Chapter-5