zoukankan      html  css  js  c++  java
  • 一起学Android之Xml与Json解析

    概述

    在网络中,数据交互通常是以XML和Json的格式进行,所以对这两种格式的数据进行解析,是Android开发中的必备功能,本文以一个简单的小例子,简述Android开发中Xml和Json解析的常用方式,仅供学习分享使用。

    XML解析

    Android 提供了三种解析XML的方式:SAX(Simple API XML), DOM(Document Object Model), PULL,本文主要讲解Pull的方式解析Xml。

    PULL解析Xml优点:PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器,Android官方推荐开发者们使用Pull解析技术。Pull解析技术是第三方开发的开源技术,它同样可以应用于JavaSE开发。

    涉及知识点

    • XmlPullParser 是一个提供对XML进行Pull方式解析的基础功能的接口。
    • xmlPullParser.getEventType()  返回当前节点的事件类型(如:START_TAG, END_TAG, TEXT, etc.)。
    • xmlPullParser.getName() 获取当前节点对应的名称。
    • xmlPullParser.getAttributeCount() 获取当前节点对应的属性个数。
    • xmlPullParser.getText() 获取当前节点对应的文本内容。
    • xmlPullParser.getAttributeName(0) 获取属性对应的名称。
    • xmlPullParser.getAttributeValue(0) 获取属性对应的值。
    • xmlPullParser.next() 移动到下一个事件。

    Xml文件

    Xml存放相对路径:DemoXmlappsrcmain esxml est.xml [xml文件夹]

     1 <bookstore>
     2     <book category="COOKING">
     3         <title lang="en">Everyday Italian</title>
     4         <author>Giada De Laurentiis</author>
     5         <year>2005</year>
     6         <price>30.00</price>
     7     </book>
     8     <book category="CHILDREN">
     9         <title lang="en">Harry Potter</title>
    10         <author>J K. Rowling</author>
    11         <year>2005</year>
    12         <price>29.99</price>
    13     </book>
    14     <book category="WEB">
    15         <title lang="en">Learning XML</title>
    16         <author>Erik T. Ray</author>
    17         <year>2003</year>
    18         <price>39.95</price>
    19     </book>
    20 </bookstore>

    Xml解析源码

     1 /**
     2      * 获取Xml内容
     3      * @param resources
     4      * @param id
     5      * @return
     6      * @throws XmlPullParserException
     7      * @throws IOException
     8      */
     9     private List<String> xml_parser(Resources resources, int id) throws XmlPullParserException, IOException {
    10         XmlPullParser xmlPullParser = resources.getXml(id);
    11         List<String> lstContent=new ArrayList<String>();
    12         int eventType = xmlPullParser.getEventType();
    13         while (eventType != XmlPullParser.END_DOCUMENT) {
    14             switch (eventType) {
    15                 case XmlPullParser.START_DOCUMENT://文档开始
    16                     Log.i(TAG, "xml_parser: START_DOCUMENT");
    17                     break;
    18                 case XmlPullParser.END_DOCUMENT://文档结束
    19                     Log.i(TAG, "xml_parser: END_DOCUMENT");
    20                     break;
    21                 case XmlPullParser.START_TAG://标记(元素,节点)开始
    22                     Log.i(TAG, "xml_parser: START_TAG");
    23                     String tagName = xmlPullParser.getName();
    24                     //有些节点是没有属性值的,所以需要判断,否则会越界
    25                     int count = xmlPullParser.getAttributeCount();//获取属性个个数
    26                     String tagAttributeValue="";
    27                     String tagAttributeName="";
    28                     //String text =xmlPullParser.getText();//此处获取不到text
    29                     String content="";
    30                     if (count > 0) {
    31                         tagAttributeName=xmlPullParser.getAttributeName(0);
    32                         tagAttributeValue = xmlPullParser.getAttributeValue(0);
    33                         content="标签="+tagName+"属性名="+tagAttributeName+"属性值="+tagAttributeValue;
    34                     }else{
    35                         content="标签="+tagName;
    36                     }
    37                     lstContent.add(content);
    38                     break;
    39                 case XmlPullParser.TEXT:
    40                     String text =xmlPullParser.getText();
    41                     lstContent.add("节点内容="+text);
    42                     break;
    43                 case XmlPullParser.END_TAG://标记结束
    44                     Log.i(TAG, "xml_parser: END_TAG");
    45                     break;
    46             }
    47             eventType = xmlPullParser.next();
    48         }
    49         return lstContent;
    50     }

    如果Xml文件过大的话,则不适合在Activity主线程中执行,本文是在Worker线程中执行的,如下所示:

     1     private static final String TAG="TAG";
     2 
     3     private static final int MSG_FINISH=0x0001;
     4 
     5     private TextView tvMsg;
     6 
     7     private Handler handler=new Handler(){
     8         @Override
     9         public void handleMessage(Message msg) {
    10             switch (msg.what){
    11                 case MSG_FINISH:
    12                     List<String> lstContent=(List<String>)msg.obj;
    13                     for (String info :lstContent){
    14                         tvMsg.append(info+"
    ");
    15                     }
    16                     break;
    17             }
    18         }
    19     };
    20 
    21     public void bn_xml_parser_click(View view){
    22         tvMsg.setText("");
    23         new Thread(){
    24             @Override
    25             public void run() {
    26                 try {
    27                     List<String> lstContent=xml_parser(getResources(),R.xml.test);
    28                     Message msg=handler.obtainMessage();
    29                     msg.what=MSG_FINISH;
    30                     msg.obj=lstContent;
    31                     handler.sendMessage(msg);
    32                 } catch (XmlPullParserException e) {
    33                     e.printStackTrace();
    34                 } catch (IOException e) {
    35                     e.printStackTrace();
    36                 }
    37             }
    38         }.start();
    39     }

    JSON解析

    Json是一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。业内主流技术为其提供了完整的解决方案,从而可以在不同平台间进行数据交换。

    涉及知识点

    • JSONObject 表示一个Json格式的对象。
    • jsonObject.getString("key"); 获取字符串格式的值。
    • jsonObject.getInt("key"); 获取Int类型的值。
    • jsonObject.getBoolean("key"); 获取bool类型的值。
    • jsonObject.getDouble("key"); 获取浮点数类型的值。
    • jsonObject.get("key"); 返回Object类型的对象。
    • jsonObject.getJSONArray("key"); 返回数据类型的对象。
    • InputStream 输入流。

    Json文件

    Json存放相对路径:DemoXmlappsrcmain es aw est2.json  [raw文件夹]

    {
      "name": "小明",
      "age": 14,
      "gender": true,
      "height": 1.65,
      "grade": null,
      "middle_school": ""W3C" Middle School",
      "skills": [
        "JavaScript",
        "Java",
        "Python",
        "Lisp"
      ]
    }

    Json解析源码

     1   /**
     2      * 解析到列表
     3      * @return
     4      * @throws IOException
     5      * @throws JSONException
     6      */
     7     private List<String> json_parser() throws IOException, JSONException {
     8         List<String> lstContent = new ArrayList<String>();
     9         String data = getContent(getResources(), R.raw.test2);
    10         JSONObject jsonObject = new JSONObject(data);
    11         String name = jsonObject.getString("name");
    12         int age = jsonObject.getInt("age");
    13         boolean gender = jsonObject.getBoolean("gender");
    14         double height = jsonObject.getDouble("height");
    15         Object grade = jsonObject.get("grade");
    16         String middleSchool = jsonObject.getString("middle_school");
    17         JSONArray jsonArray = jsonObject.getJSONArray("skills");
    18         lstContent.add("name=" + name);
    19         lstContent.add("age=" + age);
    20         lstContent.add("gender=" + gender);
    21         lstContent.add("height=" + height);
    22         lstContent.add("grade=" + grade);
    23         lstContent.add("middleSchool=" + middleSchool);
    24         for (int i = 0; i < jsonArray.length(); i++) {
    25             String skill = jsonArray.getString(i);
    26             lstContent.add("skill=" + skill);
    27         }
    28         return lstContent;
    29     }
    30 
    31     /**
    32      * 通过id获取Json文件对应的内容
    33      * @param resources
    34      * @param id
    35      * @return
    36      * @throws IOException
    37      */
    38     private String getContent(Resources resources, int id) throws IOException {
    39         StringBuilder stringBuilder = new StringBuilder();
    40         InputStream inputStream = null;
    41         try {
    42             inputStream = resources.openRawResource(id);
    43             byte[] bytes = new byte[1024];
    44             int length = inputStream.read(bytes, 0, 1024);
    45             while (length > -1) {
    46                 stringBuilder.append(new String(bytes, 0, length));
    47                 length = inputStream.read(bytes, 0, 1024);
    48             }
    49         } finally {
    50             if (inputStream != null) {
    51                 inputStream.close();
    52             }
    53         }
    54         return stringBuilder.toString();
    55     }

    同样,如果Json文件比较大,或者解析比较慢,则不能在Activity主线程中执行,需要新启动一个Worker线程,在后台执行,如下所示:

     1     private static final String TAG="TAG";
     2 
     3     private static final int MSG_FINISH=0x0001;
     4 
     5     private static  final int MSG_SERIALIZE=0x0002;
     6 
     7     private TextView tvMsg;
     8 
     9     private Handler handler=new Handler(){
    10         @Override
    11         public void handleMessage(Message msg) {
    12             switch (msg.what){
    13                 case MSG_FINISH:
    14                     List<String> lstContent=(List<String>)msg.obj;
    15                     for (String info :lstContent){
    16                         tvMsg.append(info+"
    ");
    17                     }
    18                     break;
    19             }
    20         }
    21     };
    22 
    23     /**
    24      * 解析Json
    25      * @param view
    26      */
    27     public void bn_json_parser_click(View view) {
    28         tvMsg.setText("");
    29         new Thread() {
    30             @Override
    31             public void run() {
    32                 try {
    33                     List<String> lstContent = json_parser();
    34                     Message msg = handler.obtainMessage();
    35                     msg.what = MSG_FINISH;
    36                     msg.obj = lstContent;
    37                     handler.sendMessage(msg);
    38                 } catch (IOException e) {
    39                     e.printStackTrace();
    40                 } catch (JSONException e) {
    41                     e.printStackTrace();
    42                 }
    43             }
    44         }.start();
    45     }

    如果需要将Json反序列化成类对象,或者将类对象序列化成Json格式文件,如下是一个帮助类:

      1 package com.hex.demoxml;
      2 
      3 import android.util.Log;
      4 
      5 import java.util.Collection;
      6 import java.lang.reflect.Array;
      7 import java.lang.reflect.Field;
      8 import java.lang.reflect.ParameterizedType;
      9 import java.lang.reflect.Type;
     10 import java.util.Collection;
     11 import org.json.JSONArray;
     12 import org.json.JSONException;
     13 import org.json.JSONObject;
     14 import org.json.JSONStringer;
     15 
     16 /** JSON序列化辅助类 **/
     17 public class JsonHelper {
     18     private static final String TAG="TAG";
     19     /**
     20      * 将对象转换成Json字符串
     21      **/
     22     public static String toJSON(Object obj) {
     23         JSONStringer js = new JSONStringer();
     24         serialize(js, obj);
     25         return js.toString();
     26     }
     27 
     28     /**
     29      * 序列化为JSON
     30      **/
     31     private static void serialize(JSONStringer js, Object o) {
     32         if (isNull(o)) {
     33             try {
     34                 js.value(null);
     35             } catch (JSONException e) {
     36                 e.printStackTrace();
     37             }
     38             return;
     39         }
     40 
     41         Class<?> clazz = o.getClass();
     42         if (isObject(clazz)) { // 对象
     43             serializeObject(js, o);
     44         } else if (isArray(clazz)) { // 数组
     45             serializeArray(js, o);
     46         } else if (isCollection(clazz)) { // 集合
     47             Collection<?> collection = (Collection<?>) o;
     48             serializeCollect(js, collection);
     49         } else { // 单个值
     50             try {
     51                 js.value(o);
     52             } catch (JSONException e) {
     53                 e.printStackTrace();
     54             }
     55         }
     56     }
     57 
     58     /**
     59      * 序列化数组
     60      **/
     61     private static void serializeArray(JSONStringer js, Object array) {
     62         try {
     63             js.array();
     64             for (int i = 0; i < Array.getLength(array); ++i) {
     65                 Object o = Array.get(array, i);
     66                 serialize(js, o);
     67             }
     68             js.endArray();
     69         } catch (Exception e) {
     70             e.printStackTrace();
     71         }
     72     }
     73 
     74     /**
     75      * 序列化集合
     76      **/
     77     private static void serializeCollect(JSONStringer js, Collection<?> collection) {
     78         try {
     79             js.array();
     80             for (Object o : collection) {
     81                 serialize(js, o);
     82             }
     83             js.endArray();
     84         } catch (Exception e) {
     85             e.printStackTrace();
     86         }
     87     }
     88 
     89     /**
     90      * 序列化对象
     91      **/
     92     private static void serializeObject(JSONStringer js, Object obj) {
     93         try {
     94             js.object();
     95             for (Field f : obj.getClass().getFields()) {
     96                 Object o = f.get(obj);
     97                 js.key(f.getName());
     98                 serialize(js, o);
     99             }
    100             js.endObject();
    101         } catch (Exception e) {
    102             e.printStackTrace();
    103         }
    104     }
    105 
    106     /**
    107      * 反序列化简单对象
    108      *
    109      * @throws
    110      **/
    111     public static <T> T parseObject(JSONObject jo, Class<T> clazz) {
    112         Log.i(TAG, "parseObject: >>>>>>第二个开始");
    113         if (clazz == null || isNull(jo)) {
    114             Log.i(TAG, "parseObject: >>>>>>第二个parseObject");
    115             return null;
    116         }
    117 
    118         T obj = createInstance(clazz);
    119         if (obj == null) {
    120             Log.i(TAG, "parseObject: >>>>>>创建实例为空");
    121             return null;
    122         }
    123         Log.i(TAG, "parseObject: >>>>>>属性长度"+clazz.getFields().length);
    124         Log.i(TAG, "parseObject: >>>>>>属性长度2"+clazz.getClass());
    125         for (Field f : clazz.getFields()) {
    126             Log.i(TAG, "parseObject: >>>>>>"+f.getName());
    127             setField(obj, f, jo);
    128             //Log.i(TAG, "parseObject: >>>>>>"+obj.);
    129         }
    130         Log.i(TAG, "parseObject: >>>>>返回obj"+obj.getClass());
    131         return obj;
    132     }
    133 
    134     /**
    135      * 反序列化简单对象
    136      *
    137      * @throws
    138      **/
    139     public static <T> T parseObject(String jsonString, Class<T> clazz) {
    140         if (clazz == null || jsonString == null || jsonString.length() == 0) {
    141             Log.i(TAG, "parseObject: >>>>>>>null");
    142             return null;
    143         }
    144         Log.i(TAG, "parseObject: >>>>>>>not null");
    145         JSONObject jo = null;
    146         try {
    147             jo = new JSONObject(jsonString);
    148         } catch (JSONException e) {
    149             Log.i(TAG, "parseObject: >>>>>>转换json对象异常:"+e.getMessage());
    150             e.printStackTrace();
    151         }
    152 
    153         if (isNull(jo)) {
    154             Log.i(TAG, "parseObject: >>>>>转换后为null");
    155             return null;
    156         }
    157         Log.i(TAG, "parseObject: >>>>>>进入下一步");
    158         return parseObject(jo, clazz);
    159     }
    160 
    161     /**
    162      * 反序列化数组对象
    163      *
    164      * @throws
    165      **/
    166     public static <T> T[] parseArray(JSONArray ja, Class<T> clazz) {
    167         if (clazz == null || isNull(ja)) {
    168             return null;
    169         }
    170 
    171         int len = ja.length();
    172         Log.i(TAG, "parseArray: >>>>>"+len);
    173         Log.i(TAG, "parseArray: >>>>>"+clazz.getName());
    174         @SuppressWarnings("unchecked")
    175         T[] array = (T[]) Array.newInstance(clazz, len);
    176 
    177         for (int i = 0; i < len; ++i) {
    178             try {
    179                 Object object=ja.get(i);
    180                 if(isSingle(clazz)){
    181                     Log.i(TAG, "parseArray: >>>>>:"+object.toString());
    182                     array[i]=(T)object.toString();
    183                 }else {
    184                     JSONObject jo = ja.getJSONObject(i);
    185                     Log.i(TAG, "parseArray: >>>>>jo:"+jo.toString());
    186                     T o = parseObject(jo, clazz);
    187                     Log.i(TAG, "parseArray: >>>>>o:" + o.toString());
    188                     array[i] = o;
    189                 }
    190             } catch (JSONException e) {
    191                 e.printStackTrace();
    192             }
    193         }
    194 
    195         return array;
    196     }
    197 
    198     /**
    199      * 反序列化数组对象
    200      *
    201      * @throws
    202      **/
    203     public static <T> T[] parseArray(String jsonString, Class<T> clazz) {
    204         if (clazz == null || jsonString == null || jsonString.length() == 0) {
    205             return null;
    206         }
    207         JSONArray jo = null;
    208         try {
    209             jo = new JSONArray(jsonString);
    210         } catch (JSONException e) {
    211             e.printStackTrace();
    212         }
    213 
    214         if (isNull(jo)) {
    215             return null;
    216         }
    217 
    218         return parseArray(jo, clazz);
    219     }
    220 
    221     /**
    222      * 反序列化泛型集合
    223      *
    224      * @throws
    225      **/
    226     @SuppressWarnings("unchecked")
    227     public static <T> Collection<T> parseCollection(JSONArray ja, Class<?> collectionClazz,
    228                                                     Class<T> genericType) {
    229 
    230         if (collectionClazz == null || genericType == null || isNull(ja)) {
    231             return null;
    232         }
    233 
    234         Collection<T> collection = (Collection<T>) createInstance(collectionClazz);
    235 
    236         for (int i = 0; i < ja.length(); ++i) {
    237             try {
    238                 JSONObject jo = ja.getJSONObject(i);
    239                 T o = parseObject(jo, genericType);
    240                 collection.add(o);
    241             } catch (JSONException e) {
    242                 e.printStackTrace();
    243             }
    244         }
    245 
    246         return collection;
    247     }
    248 
    249     /**
    250      * 反序列化泛型集合
    251      *
    252      * @throws
    253      **/
    254     public static <T> Collection<T> parseCollection(String jsonString, Class<?> collectionClazz,
    255                                                     Class<T> genericType) {
    256         if (collectionClazz == null || genericType == null || jsonString == null
    257                 || jsonString.length() == 0) {
    258             return null;
    259         }
    260         JSONArray jo = null;
    261         try {
    262             jo = new JSONArray(jsonString);
    263         } catch (JSONException e) {
    264             e.printStackTrace();
    265         }
    266 
    267         if (isNull(jo)) {
    268             return null;
    269         }
    270 
    271         return parseCollection(jo, collectionClazz, genericType);
    272     }
    273 
    274     /**
    275      * 根据类型创建对象
    276      **/
    277     private static <T> T createInstance(Class<T> clazz) {
    278         if (clazz == null)
    279             return null;
    280         T obj = null;
    281         try {
    282             obj = clazz.newInstance();
    283         } catch (Exception e) {
    284             Log.i(TAG, "createInstance: >>>>>>创建实例异常");
    285             e.printStackTrace();
    286         }
    287         return obj;
    288     }
    289 
    290     /**
    291      * 设定字段的值
    292      **/
    293     private static void setField(Object obj, Field f, JSONObject jo) {
    294         String name = f.getName();
    295         Class<?> clazz = f.getType();
    296         Log.i(TAG, "setField: >>>>>name:"+name);
    297         try {
    298             if (isArray(clazz)) { // 数组
    299                 Log.i(TAG, "setField: >>>>>数组");
    300                 Class<?> c = clazz.getComponentType();
    301                 JSONArray ja = jo.optJSONArray(name);
    302                 if (!isNull(ja)) {
    303                     Log.i(TAG, "setField: >>>>>ja:"+ja.getString(0));
    304                     Object array = parseArray(ja, c);
    305                     f.set(obj, array);
    306                 }else{
    307                     Log.i(TAG, "setField: >>>>>数组为空");
    308                 }
    309             } else if (isCollection(clazz)) { // 泛型集合
    310                 Log.i(TAG, "setField: >>>>>泛型集合");
    311                 // 获取定义的泛型类型
    312                 Class<?> c = null;
    313                 Type gType = f.getGenericType();
    314                 if (gType instanceof ParameterizedType) {
    315                     ParameterizedType ptype = (ParameterizedType) gType;
    316                     Type[] targs = ptype.getActualTypeArguments();
    317                     if (targs != null && targs.length > 0) {
    318                         Type t = targs[0];
    319                         c = (Class<?>) t;
    320                     }
    321                 }
    322 
    323                 JSONArray ja = jo.optJSONArray(name);
    324                 if (!isNull(ja)) {
    325                     Object o = parseCollection(ja, clazz, c);
    326                     f.set(obj, o);
    327                 }
    328             } else if (isSingle(clazz)) { // 值类型
    329                 Log.i(TAG, "setField: >>>>>Single值类型");
    330                 Object o = jo.opt(name);
    331                 if (o != null) {
    332                     f.set(obj, o);
    333                 }
    334             } else if (isObject(clazz)) { // 对象
    335                 Log.i(TAG, "setField: >>>>>Object对象:"+clazz);
    336                 JSONObject j = jo.optJSONObject(name);
    337                 if (!isNull(j)) {
    338 
    339                     Object o = parseObject(j, clazz);
    340                     f.set(obj, o);
    341                 }else{
    342                     Log.i(TAG, "setField: >>>>>Object对象为null");
    343                 }
    344             } else {
    345                 Log.i(TAG, "setField: >>>>>未知类型:"+clazz);
    346                 throw new Exception("unknow type!");
    347             }
    348         } catch (Exception e) {
    349             e.printStackTrace();
    350         }
    351     }
    352 
    353     /**
    354      * 判断对象是否为空
    355      **/
    356     private static boolean isNull(Object obj) {
    357         if (obj instanceof JSONObject) {
    358             return JSONObject.NULL.equals(obj);
    359         }
    360         return obj == null;
    361     }
    362 
    363     /**
    364      * 判断是否是值类型
    365      **/
    366     private static boolean isSingle(Class<?> clazz) {
    367         return isBoolean(clazz) || isNumber(clazz) || isString(clazz);
    368     }
    369 
    370     /**
    371      * 是否布尔值
    372      **/
    373     public static boolean isBoolean(Class<?> clazz) {
    374         return (clazz != null)
    375                 && ((Boolean.TYPE.isAssignableFrom(clazz)) || (Boolean.class
    376                 .isAssignableFrom(clazz)));
    377     }
    378 
    379     /**
    380      * 是否数值
    381      **/
    382     public static boolean isNumber(Class<?> clazz) {
    383         return (clazz != null)
    384                 && ((Byte.TYPE.isAssignableFrom(clazz)) || (Short.TYPE.isAssignableFrom(clazz))
    385                 || (Integer.TYPE.isAssignableFrom(clazz))
    386                 || (Long.TYPE.isAssignableFrom(clazz))
    387                 || (Float.TYPE.isAssignableFrom(clazz))
    388                 || (Double.TYPE.isAssignableFrom(clazz)) || (Number.class
    389                 .isAssignableFrom(clazz)));
    390     }
    391 
    392     /**
    393      * 判断是否是字符串
    394      **/
    395     public static boolean isString(Class<?> clazz) {
    396         return (clazz != null)
    397                 && ((String.class.isAssignableFrom(clazz))
    398                 || (Character.TYPE.isAssignableFrom(clazz)) || (Character.class
    399                 .isAssignableFrom(clazz)));
    400     }
    401 
    402     /**
    403      * 判断是否是对象
    404      **/
    405     private static boolean isObject(Class<?> clazz) {
    406         return clazz != null && !isSingle(clazz) && !isArray(clazz) && !isCollection(clazz);
    407     }
    408 
    409     /**
    410      * 判断是否是数组
    411      **/
    412     public static boolean isArray(Class<?> clazz) {
    413         return clazz != null && clazz.isArray();
    414     }
    415 
    416     /**
    417      * 判断是否是集合
    418      **/
    419     public static boolean isCollection(Class<?> clazz) {
    420         return clazz != null && Collection.class.isAssignableFrom(clazz);
    421     }
    422 }
    View Code

    备注

    沉舟侧畔千帆过,病树前头万木春。

  • 相关阅读:
    垂直同步
    C++ RAII
    C++ RAII
    LCD刷新率和垂直同步的设置
    ping结果中TTL是什么意思
    垂直同步
    stage.frameRate改变帧频
    ping结果中TTL是什么意思
    stage.frameRate改变帧频
    ping 命令的原理,揭开单向“Ping”通的奥秘
  • 原文地址:https://www.cnblogs.com/hsiang/p/11295574.html
Copyright © 2011-2022 走看看