Retrofit响应数据及异常处理策略

今天我们来谈谈客户端对通讯协议的处理,主要分为三部分:约定响应数据格式,响应数据的自动映射以及错误处理三部分。由于数据协议采用json的居多,因此我们在此基础上进行说明。

约定响应数据格式

协议格式

通常来说,你拿到的设计文档中会存在通信协议的说明,对于客户端来说,一个良好的通信协议需要能描述操作状态(操作码+操作提示)以操作结果,因此,常见的响应数据的格式如下:

{
  "code": 0,
  "msg": "正常",
  "data": {
    "id": 1,
    "account": "121313",
    "accountName": "alipay",
    "income": "600.000000"
  }
}

code定义

code为我们自定义的操作状态码,首先来看我们常用的定义:

code 说明
0 操作成功的消息提示
1 客户端认证失败,一般是客户端被恶意修改
2 用户认证失败
3 提交参数错误:参数缺失、参数名不对等
4 提交参数校验失败,一般是提交给服务端的数据格式有误,多发生在表单提交的场景中
5 自定义错误,服务端发生不可恢复的错误等

msg定义

msg为服务器端返回的操作信息。

无论操作成功与否,客户端都应该根据业务给出准确的提示,客户端则根据实际情况选择展示与否。

data 定义

data则是请求返回的具体内容,通常data根据请求接口的不同最终会被解析成不同的实体类。

示例

下面我们以获取消息列表和消息详情两个接口返回的响应数据作为示例:

消息列表:

{
    "code": 0,
    "data": {
        "list": [
            {
                "content": "你参加的活动已经开始了...",
                "createtime": "2016-09-23 16:44:02",
                "id": "4480",
                "status": 0,
                "title": "活动开始",
                "type": "1"
            },
            {
                "content": "你参加的活动已经结束...",
                "createtime": "2016-09-19 14:30:02",
                "id": "4444",
                "status": 0,
                "title": "活动结束",
                "type": "1"
            }
        ],
        "total": 2
    },
    "msg": "正常"
}

消息详情

{
    "code": 0,
    "data": {
        "detail":
            {
                "content": "你参加的活动已经开始了,请准时到你的活动中去执行",
                "createtime": "2016-09-23 16:44:02",
                "id": "4480",
                "status": 0,
                "title": "活动开始",
                "type": "1"
            },

    },
    "msg": "正常"
}

响应数据映射实体数据模型

当我们接受到如上格式的响应数据时,下面便是考虑如何应用的问题,也就是如何将协议转换?是在获取响应的时候自动转换还是手动转换?转换成java实体类还是String?

“偷懒”是程序员的天性,我们当然不希望花费时间在这种无创造性的工作上,所以我们考虑在收到响应的时候直接将其转换为java实体类。

确定了我们的目标之后,接下来,首要任务是对数据协议进行抽象?什么叫做数据协议抽象?

所谓的数据协议抽象就是根据聚合性,通用性,隔离性三原则将整个数据协议进行切分复用,以便更好的映射成我们需要的数据模型。

我们对刚才约定的数据协议格式进行协议抽象后,可以拿到类似以下的实体模型:

public class Result<T> {
    private int code;
    private String msg;
    private T data;

    //...set和get方法
}

Result做为所有响应模型的公共基类,其中的code,msg,data分别用来映射我们通信协议。其中,泛型化的data确保接受不同的实体模型,可以看出,我们通过数据协议抽象之后,最终得到了一个良好的数据模型。

为了下面的需要我们一同将消息列表和消息详情的实体类放上来:

public class message{

    private String content;
    private String createtime;
    private String id;
    private int status;
    private String title;
    private String type;

    //...set和get方法
}
public class messageList {
    private int total;
    private List<Message> list;

    //...set和get方法

}

现在来看看我们理想的获取消息列表和获取消息详情的接口应该是什么样的:

@GET("/user/message/list")
Call<Result<MessageList>> getMessageList(@Query("page") int page);

@GET("/user/message")
Call<Result<Message>> getMessage(@Query("mid") int mid);

结合我们上面所述,我们希望每个api最后返回给我们的都是Result

provided ‘com.google.code.gson:gson:2.7‘

接下来是添加Converter依赖:

com.squareup.retrofit2:converter-gson

最后为retrofit设置Converter:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

这样,我们的请求和响应由Gson进行处理:请求体(使用@Body)被映射成json,响应体被映射成实体数据模型。

上面我们谈到了通讯协议格式以及如何利用retrofit的Converter实现协议和实体之间的自动映射。此时我们调用任何服务接口其使用大体如下,以获取消息列表接口为例:

   Call<Result<MessageList>> call = ApiFactory.getUserApi().getMessageList(mCurrentPage * getPageSize(), getPageSize());
        call.enqueue(new Callback<Result<MessageList>>() {
            @Override
            public void onResponse(Call<Result<MessageList>> call, Response<Result<MessageList>> response) {
                Result<MessageList> result = response.body();
                if (result.isOk()) {//操作正确

                } else {//操作失败
                    switch (result.getCode()) {
                        case 1:

                            break;
                        case 2:

                            break;
                        case 3:
                            break;
                        case 4:
                            break;
                        case 5:

                            break;
                    }
                }
            }

            @Override
            public void onFailure(Call<Result<MessageList>> call, Throwable t) {
                //响应失败
            }
        });

错误处理

引入RxJava之前哪点事

按道理说,retrofit讲到这里已经足够了,在此基础上在进行二次封装形成自己的框架也很不错。但是由于RxJava发展确实不错,因此retrofit引入对rxjava的支持,二者的有效结合才能发挥更强大的力量。

不了解RxJava同学可以就此打住或者先去了解相关资料。rxjava并无多大难度,明白理论之后再加上多练即可。对rxjava实现感兴趣的童鞋可以参看去年写的教你写响应式框架

再来说说,在新项目开始的时候,我为什么选择引入rxjava,不引入不行么?

我并未考虑引入rxjava的原因我只想使用retrofit这个网络请求库代替原有的async-http-client,后面发现引入rxjava能够非常容易的帮助我们进行线程切换以及合理的处理网络异常。

如何引入rxjava?

引入rxjava非常简单,需要添加以下依赖:

 compile ‘io.reactivex:rxjava:1.1.0‘
 compile ‘io.reactivex:rxandroid:1.1.0‘

接下来还需要引入adapter来将retrofit中Call转换为rxjava中的Observable:

compile ‘com.squareup.retrofit2:adapter-rxjava:2.0.2‘

最后需要在代码中启用该adapter:

Retrofit.Builder  mBuilder = new
Retrofit.Builder().addCallAdapterFactory(RxJavaCallAdapterFactory.create())

现在看引入RxJava之后接口的变化,同样还是以获取消息列表为例:

引入之前:

@GET("/user/message/list")
Call<Result<MessageList>> getMessageList(@Query("start") int start, @Query("length") int length);

引入之后:

@GET("/user/message/list")
Observable<Result<MessageList>> getMessageList(@Query("start") int start, @Query("length") int length);

得益于retrofit良好的设计,加入对rxjava的支持对我们接口的影响非常之小。

自定义Converter统一错误处理

我们对异常总是感觉麻烦,在客户端开发中,网络异常更是重中之重。现在让我们回到开始,来看这段代码:

  ApiFactory.getUserApi().getMessageList(0, 10).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Result<MessageList>>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        //handle throwable
                    }

                    @Override
                    public void onNext(Result<MessageList> result) {
                        if (result.isOk()) {
                            MessageList messageList = result.getData();
                            //handle messageList
                        }else{
                            int code = result.getCode();
                            switch (code) {
                                case 1:

                                    break;
                                case 2:

                                    break;
                                case 3:
                                    break;
                                case 4:
                                    break;
                                case 5:

                                    break;
                            }
                        }
                    }
                });

看起很棒,我们用了rxjava中线程切换避免以往繁琐的操作。但是好像不是那么完美:在rxjava中,所有的异常都是放在onError(),而这里的onNext()好像不是那么纯粹,既要承担正常业务逻辑还是处理异常的错误逻辑,换言之,onNext()干了onError()的事情,这看起来很不协调?另外,如果每个接口都要这么做,不但繁琐而且还会长城很多重复性的代码,长久以往,整个项目的工程质量将无法把控。

实际上,我们希望所有的异常都是统一在onError()中进行处理。那么这里我们急需要明确下异常的范围:响应数据中code非0的情况以及其他异常。为了更好描述code非0的情况,我们定义ApiException异常类:

public class ApiException extends RuntimeException {
    private int errorCode;

    public ApiException(int code, String msg) {
        super(msg);
        this.errorCode = code;
    }

    public int getErrorCode() {
        return errorCode;
    }

}

另外为了更好描述code,我们也将其定义成ApiErrorCode:

public interface ApiErrorCode {
    /** 客户端错误*/
    int ERROR_CLIENT_AUTHORIZED = 1;
    /** 用户授权失败*/
    int ERROR_USER_AUTHORIZED = 2;
    /** 请求参数错误*/
    int ERROR_REQUEST_PARAM = 3;
    /** 参数检验不通过 */
    int ERROR_PARAM_CHECK = 4;
    /** 自定义错误*/
    int ERROR_OTHER = 10;
    /** 无网络连接*/
    int ERROR_NO_INTERNET = 11;

}

现在问题就是如何将ApiException纳入到rxjava的onError()当中,也就是在哪里抛出该类异常。retrofit中的Converter承担了协议映射的功能,而ApiException只有在映射之后才能抛出,因此Converter是抛出ApiException的切入点。

先来对Converter接口有个初步的了解,其源码如下:


public interface Converter<F, T> {
  T convert(F value) throws IOException;

  //用于创建Converter实例
  abstract class Factory {

    //响应体转换
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    //请求体转换
    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
  }
}

接下来,我们从retrofit提供的converter-gson的实现看起.

其结构非常简单:GsonConverterFactory,

GsonRequestBodyConverter及GsonResponseBodyConverter,分别来看一下起源码:

GsonRequestBodyConverter源码:

//请求体转换
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
  private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
  private static final Charset UTF_8 = Charset.forName("UTF-8");

  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public RequestBody convert(T value) throws IOException {
    Buffer buffer = new Buffer();
    Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
    JsonWriter jsonWriter = gson.newJsonWriter(writer);
    adapter.write(jsonWriter, value);
    jsonWriter.close();
    return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
  }
}

GsonResponseBodyConverter源码:

//响应体转换
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(TypeAdapter<T> adapter) {
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    try {
      return adapter.fromJson(value.charStream());
    } finally {
      value.close();
    }
  }
}

GsonConverterFactory源码:

//转换器
public final class GsonConverterFactory extends Converter.Factory {
private final Gson gson;

  public static GsonConverterFactory create() {
    return create(new Gson());
  }

  public static GsonConverterFactory create(Gson gson) {
    return new GsonConverterFactory(gson);
  }

  private GsonConverterFactory(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    this.gson = gson;
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(adapter);//创建响应转换器
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);//创建请求转换器
  }
}

到这里我们已经有思路了:我们需要在修改GsonResponseBodyConverter,在其中加入抛出ApiException的代码.仿照converter-gson结构,我们自定义custom-converter-gson:

仿照GsonResponseBodyConverter编写MyGsonResponseBodyConverter:

public class MyGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private static final Charset UTF_8 = Charset.forName("UTF-8");
    private final Gson mGson;
    private final TypeAdapter<T> adapter;

    public MyGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        mGson = gson;
        this.adapter = adapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        String response = value.string();
        Result re = mGson.fromJson(response, Result.class);
        //关注的重点,自定义响应码中非0的情况,一律抛出ApiException异常。
        //这样,我们就成功的将该异常交给onError()去处理了。
        if (!re.isOk()) {
            value.close();
            throw new ApiException(re.getCode(), re.getMsg());
        }

        MediaType mediaType = value.contentType();
        Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
        ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes());
        InputStreamReader reader = new InputStreamReader(bis,charset);
        JsonReader jsonReader = mGson.newJsonReader(reader);
        try {
            return adapter.read(jsonReader);
        } finally {
            value.close();
        }
    }
}

仿照GsonRequestBodyConverter编写MyGsonRequestBodyConverter:

public class MyGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private final Gson gson;
    private final TypeAdapter<T> adapter;

    public MyGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public RequestBody convert(T value) throws IOException {
        Buffer buffer = new Buffer();
        Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
        JsonWriter jsonWriter = gson.newJsonWriter(writer);
        adapter.write(jsonWriter, value);
        jsonWriter.close();
        return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
    }
}

仿照GsonConverterFactory编写MyGsonConverterFactory:

public class MyGsonConverterFactory extends Converter.Factory {
    private final Gson gson;

    private MyGsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    public static MyGsonConverterFactory create() {
        return create(new Gson());
    }

    public static MyGsonConverterFactory create(Gson gson) {
        return new MyGsonConverterFactory(gson);
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new STGsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new STGsonRequestBodyConverter<>(gson, adapter);
    }
}

接下来只需要在的Retrofit中使用MyGsonConverterFactory即可:

Retrofit.Builder  mBuilder = new
Retrofit.Builder().addConverterFactory(MyGsonConverterFactory.create())
                //.addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())

通过上面的改进,我们已经成功的将所有异常处理点转移至onError()当中了。这时,我们再来对比一下获取消息列表接口的使用:

  ApiFactory.getUserApi().getMessageList(0, 10).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Result<MessageList>>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        if(e instanceof HttpException){
                            //handle
                        }else if(e instance of IOExcepton){
                            //handle
                        }else if(e instanceof ApiException){
                            ApiException exception=(ApiException)e;
                            int code = result.getErrorCode();
                            switch (code) {
                                case ApiErrorCode.ERROR_CLIENT_AUTHORIZED:
                                    //handle
                                    break;
                                case ApiErrorCode.ERROR_USER_AUTHORIZED:
                                    //handle
                                    break;
                                case ApiErrorCode.ERROR_REQUEST_PARAM:
                                    //handle
                                    break;
                                case ApiErrorCode.ERROR_PARAM_CHECK:
                                    //handle
                                    break;
                                case ApiErrorCode.ERROR_OTHER:
                                    //handle
                                    break;
                                case ApiErrorCode.ERROR_NO_INTERNET:
                                    //handle
                                    break;
                        }else{
                            //handle
                        }
                    }

                    @Override
                    public void onNext(Result<MessageList> result) {
                         MessageList messageList = result.getData();
                        //handle messageList
                        }
                    }
                });

到现在,已经解决了统一异常处理点的问题,接下来便是要解决公共异常。不难发现,对于大部分网络异常来说,我们处理策略是相同的,因此我们希望抽取公共异常处理。除此之外,在网络真正请求之前,需要对网络进行判断,无网络的情况下直接抛出响应异常。

这时候就需要自定BaseSubscriber,并在其中做相关的处理:

public class BaseSubscriber<T> extends Subscriber<T> {
    private Context mContext;

    public BaseSubscriber() {
    }

    public BaseSubscriber(Context context) {
        mContext = context;
    }

    @Override
    public void onStart() {
        //请求开始之前,检查是否有网络。无网络直接抛出异常
        //另外,在你无法确定当前代码运行在什么线程的时候,不要将UI的相关操作放在这里。
        if (!TDevice.hasInternet()) {
            this.onError(new ApiException(ApiErrorCode.ERROR_NO_INTERNET, "network interrupt"));
            return;
        }

    }

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {
        ApiErrorHelper.handleCommonError(mContext, e);
    }

    @Override
    public void onNext(T t) {

    }
}

//辅助处理异常
public class ApiErrorHelper {

    public static void handleCommonError(Context context, Throwable e) {
        if (e instanceof HttpException) {
            Toast.makeText(context, "服务暂不可用", Toast.LENGTH_SHORT).show();
        } else if (e instanceof IOException) {
              Toast.makeText(context, "连接失败", Toast.LENGTH_SHORT).show();
        } else if (e instanceof ApiException) {
           //ApiException处理
        } else {
             Toast.makeText(context, "未知错误", Toast.LENGTH_SHORT).show();
        }
    }

}

现在再来看看获取消息列表接口的使用

    ApiFactory.getUserApi().getMessageList(mCurrentPage * getPageSize(), getPageSize()).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new BaseSubscriber<Result<MessageList>>() {

                    @Override
                    public void onNext(Result<MessageList> result) {
                        MessageList messageList = result.getData();
                        //handle messageList

                    }
                });

大部分接口的使用都和以上类似,针对个别异常处理只需要重写onError()方法即可。

时间: 2024-10-05 04:24:26

Retrofit响应数据及异常处理策略的相关文章

Java框架之SpringMVC 03-RequestMapping-请求数据-响应数据

SpringMVC SpringMVC是一种轻量级的.基于MVC的Web层应用框架. 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无须实现任何接口. 采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性. 优点: 1.天生与Spring框架集成,如:(IOC,AOP) 2.支持Restful风格 3.支持灵活的URL到页面控制器的映射 4.非常容易与其他视图技术集成,如:Velocity.FreeMarker等等 5.因为模型数据不存放在特定的API里,而是放在

用 jQuery.ajaxSetup 实现对请求和响应数据的过滤

不知道同学们在做项目的过程中有没有相同的经历呢?在使用 ajax 的时候,需要对请求参数和响应数据进行过滤处理,比如你们觉得就让请求参数和响应信息就这么赤裸裸的在互联网里来回的穿梭,比如这样: 要知道,在浩瀚的互联网中,所有的信息都是不安全的,万一有人偷窥我们怎么办?!万一被别人看见了我们的美体,偷窥到了我们的私处,然后以此威胁我们,岂不是太难堪了不是?这时,你或许会想给请求数据和响应数据加密,就相当于给我们的数据穿上了一层衣服.于是我们这样: 是不是美美哒,对,穿上一层漂漂亮亮的衣服,就不怕别

Hibernate学习---第十一节:Hibernate之数据抓取策略&amp;批量抓取

1.hibernate 也可以通过标准的 SQL 进行查询 (1).将SQL查询写在 java 代码中 /** * 查询所有 */ @Test public void testQuery(){ // 基于标准的 sql 语句查询 String sql = "select * from t_person"; // 通过 createSQLQuery 获取 SQLQuery,而 SQLQuer 是 Query的子类 SQLQuery query = session.createSQLQue

jQuery-1.9.1源码分析系列(十六)ajax——响应数据处理和api整理

ajax在得到请求响应后主要会做两个处理:获取响应数据和使用类型转化器转化数据 a.获取响应数据 获取响应数据是调用ajaxHandleResponses函数来处理. ajaxHandleResponses的功能有: - 为jqXHR设置所有responseXXX字段(值便是响应数据) - 找到正确的dataType (在content-type和预期的dataType两者中的一个) - 返回正确的响应数据 我们看一个响应数据的格式: responses = { text: "{"co

Linux -- 服务器数据备份恢复策略

一.Linux 备份恢复基础 1.什么是备份 最简单的讲,备份数据的过程就是拷贝重要的数据到其他的介质之上(通常是可移动的),以保证在原始数据丢失的情况下可以恢复数据.一次备份可能是简单的 cp命令,将一个文件复制到其他目录下,也可能是使用特定的程序将数据流写进一个特定的设备中的复杂过程.很多情况下是将要备份的数据写入到磁带机中,但有些情况也不是这样的.在Linux环境下,或其他Unix系统,备份可以是将文件拷贝到已存在的文件系统,可替换的文件系统,磁带机,远程文件系统,甚至是远程系统的上的磁带

Fiddler-006-修改HTTP请求响应数据

在进行 App 测试时,经常需要修改请求参数,以获得不同的显示效果,以查看相应的页面显示处理.例如:电商购物App中商品都有好几种状态(在售.缺货.暂不销售.下 架等).同时,一般这几种状态均为同一个按钮对应的多种情况,那么测试商品详情时,需要测试按钮在商品不同状态下的显示效果,就需要构建不同状态的商品, 若是多人测试不同的状态下的操作,那么就不能使用同一件商品同时进行测试,因为测试时,需要修改商品的状态,那么多人测试时会有影响.此时,测试页面显示 的朋友,则可以通过修改获取商品详情的HTTP请

AJAX--接收服务器端的响应数据

* 接收服务器端的响应数据 * 使用XMLHttpRequest核心对象的responseText属性 * 该属性只能接收文本(HTML)格式 * 问题 * 解析过程比较复杂(拆串) * 拆串或拼串极容易出错 * XML格式 * 基本内容 * HTML.XHTML.DHTML和XML的区别 * HTML就是网页 - 元素定义大小写 * XHTML就是严格意义的HTML - 元素定义小写 * DHTML - BOM|DOM * XML - 配置文件|数据格式 * XML文件的扩展名为".xml&q

EMVTag系列16——AC响应数据

在一个联机交易中,要传送到发卡行的专有应用数据. 字段 长度(字节) 赋值 说明 长度 1 07 分散密钥索引 1 00 密文版本号 1 01 根据发卡行密钥版本设置 卡片验证结果(CVR) 4 03 00 bits 8–7: 00 = 第2个GENERATE AC返回AAC 01 = 第2个GENERATE AC返回TC 10 = 不请求第2个GENERATE AC 11 = RFU bits 6–5: 00 = 第1个GENERATE AC返回AAC 01 = 第1个GENERATE AC返

XML(php中获取xml文件的方式/ajax获取xml格式的响应数据的方式)

1.XML 格式规范: ① 必须有一个根元素 ② 不可有空格.不可以数字或.开头.大小写敏感 ③ 不可交叉嵌套 ④ 属性双引号(浏览器自动修正成双引号了) ⑤ 特殊符号要使用实体 ⑥ 注释和HTML一样 虽然可以描述和传输复杂数据,但是其解析过于复杂并且体积较大,所以实现开发已经很少使用了.   例: <?xml version="1.0" encoding="UTF-8"?> <root> <arrayList> <arr