Android解析数据—XML格式数据

1.前言

           目前的绝大多数移动端的应用都需要访问网络,既然需要访问网络就必须有一个自己的服务器,应用可以向服务器提交数据也可以从服务器上获取数据。这个时候就会出现一个问题,这些数据到是是以什么样的格式与服务器进行交流呢?随随便便的一段文本肯定不行,因此,一般我们会在网络上传输一些有着一定的结构和语义的的数据,当一方接收到这种数据后就可以按照相应的结构和语义进行解析,读取到相应的内容。

       在网络上传输数据最常用的格式主要有两种:XML和JSON。最近公司在做项目的过程中需要大量的调用WebService接口,在调用接口后就需要对返回XML格式的数据进行解析,因此我在这里做一个数据解析的总结(此篇文章仅仅总结解析XML格式数据的方法,JSON的数据解析将在下一篇文章中总结),使自己加强印象的同时也希望能够帮助到看到这篇文章程序员朋友们。

2.数据分析

(1)XML格式

       XML(Extensible Markup Language)扩展标记语言,是用于标记电子文件使其具有结构性的标记语言,可以用来标记数据和定义数据类型,有着格式统一,可以跨平台容易与其它系统进行远程交互,数据共享方便等优点。但是XML文件庞大,文件格式复杂,传输占用带宽较多,在服务端和客户端都需要花费大量的代码来解析XML,导致在服务端和客户端的代码变得异常复杂且不容易维护,所以在开发移动端的应用是不建议使用XML格式的数据进行传输。

(2)JSON格式

       JSON(JavaScript Object Notation)与XML格式相比较是一种轻量级的数据交换格式,JSON采用兼容性很高,完全独立于语言文本格式,可以在不同的平台之间进行数据交换。JSON数据格式比较简单,易于读写,格式都是压缩的,在传输的过程中占用带宽小并且它还支持多种语言,便于服务端和客户端的解析同时因为JSON格式能够直接为服务器端代码使用,大大简化了服务端和客户端的代码并且易于维护。

3.实现方法

       上面提到过JSON相对与XML来说是轻量级的数据,那么XML相对与JSON的重量级体现在哪呢?应该体现在解析上,XML目前比较常用的有三种解析方式:Pull解析,SAX解析和DOM解析。

(1)SAX

       SAX解析非常适用于在移动设备的开发,它是树形结构解析,有着解析速度快并且占用内存少,但是它在解析的过程在会预加载整个文档,适用于文档较小的情况(SAX支持已经内置道JDK1.5中,不需要添加任何的jar文件)。SAX解析XML格式的数据采用的是事件驱动的方式,也就是说它不需要解析完整个文档,在被解析的文档中是按内容顺序进行解析的,在解析的过程中SAX会判断当前读到的字段是否合法,如果合法就会触发相应的事件。所谓的这些被触发的事件其实就是一些回调方法(callback),这些方法定义在ContentHandler接口中,下面是一些接口常用的方法。

      startDocument()

      这个方法是在开始XML解析的时候调用的,可以在其中做一些预处理工作。

      startElement(String uri, String localName, String qName, Attributes attributes)

      这个方法会在开始解析某个节点的时候调用,当读到一个开始节点的时候会触发这个方法,这个方法中一共有4个参数,uri参数是命名空间,localName参数记录着当前节点的名字,qName参数记录着带有命名空间节点的名字,通过attributes参数可以得到所有的属性名和相应的值。

     characters(char[] ch, int start, int length)

     这个方法用来处理在XML文件中读到的内容,这个方法一共有3个参数,ch参数为文件的字符串的内容,start参数是读到的字符串在这个数组中的起始位置,length参数是读到的字符串在这个数组中的长度,使用new String(ch, start, length)就可以获取内容。

     endElement(String uri, String localName, String qName)

     这个方法与startElement()方法相对应,在遇到结束节点标记的时候会调用这个方法。

     endDocument()

     这个方法与startDocument()方法相对应,在文档结束的时候调用这个方法,可以在其中做一些善后工作。

     其中要特别注意的是SAX的一个重要的特点就是他是流处理模式的,当遇到一个节点的时候,它并不会记录下以前的所遇到的节点,也就是说在startElement()方法中你所知道的的信息就是当前节点的名字和属性,至于节点的嵌套结构,上层节点的名字,是否含有子节点等等其它与结构相关的信息,我们都是无法得知的,都需要在程序中去完成,这样就使得SAX解析在编程处理上没有DOM解析来的那么方便

    SAX是基于事件驱动的,android的事件机制是基于回调函数的,在使用SAX解析XML文档的时候,在读取到文档开始和结束节点的时候就会回调一个事件,在读取到其它节点与内容的时候也会回调一个事件。在编写程序的过程中只要为SAX提供实现ContentHandler接口的类,那么该类就会拥有解析XML文档一系列的回调方法,因为ContentHandler是一个接口,再使用的时候会有些不方便,因此,SAX还为其制定了一个DefaultHandler类,它实现了ContentHandler的接口,但是里面所有的方法体都为空,在实现的过程中我们只需要继承这个类并且重写相对应的方法即可。


要进行解析的xml文件如下

<?xml version="1.0" encoding="UTF-8"?>
<rt>
<tid>20140318155513001</tid>
<warehouseid>47a7377d4a9e42e3a665af0894946e21</warehouseid>
<rc>0000</rc>
<rm>成功</rm>
<tasks>
<task>
<name>102</name>
<refercode>150303101458414</refercode>
<status>3</status>
</task>
<task>
<name>119</name>
<refercode>20150303-6</refercode>
<status>3</status>
</task>
<task>
<name>202</name>
<refercode>150307125515568</refercode>
<status>3</status>
</task>
<task>
<name>107</name>
<refercode>150303140159283</refercode>
<status>3</status>
</task>
</tasks>
</rt>

解析数据的主要代码如下

package com.example.administrator.xmlparser;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;

/**
 * Created by ChuPeng on 2016/9/12.
 */
public class ParserXMLWithSAX
{
    private List<Goods> goodsList;
    public static List<Goods> getXmlContentForSAX(String xml)
    {
        try
        {
            //创建一个解析XML的工厂对象
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //创建一个解析XML的对象
            XMLReader xmlReader = factory.newSAXParser().getXMLReader();
            //创建一个解析助手类
            ParserXMLWithSAXHandler handler = new ParserXMLWithSAXHandler();
            //将ParserXMLWithSAXHandler的实例设置到XMLReader中
            xmlReader.setContentHandler(handler);
            //开始解析
            xmlReader.parse(new InputSource(new StringReader(xml)));
            return handler.getList();
        } catch (SAXException e)
        {
            e.printStackTrace();
        }
        catch (ParserConfigurationException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {

        }
        return null;
    }
}



package com.example.administrator.xmlparser;

import android.util.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by ChuPeng on 2016/9/19.
 */
public class ParserXMLWithSAXHandler extends DefaultHandler
{
    //储存所有解析的元素
    private List<Goods> goodsList;
    //储存正在解析的元素数据
    private Goods goods;
    private String nodeName;

    public List<Goods> getList()
    {
        return goodsList;
    }

    public void startDocument() throws SAXException
    {
        //初始化
        goodsList = new ArrayList<Goods>();
        Log.d("SAX", "--startDocument()--");
    }

    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
    {
        nodeName = localName;
        if("task".equals(nodeName))
        {
            goods = new Goods();
        }
        Log.d("SAX", "--startElement()--"+qName);
    }

    public void characters(char[] ch, int start, int length) throws SAXException
    {

        if("name".equals(nodeName))
        {
            goods.setName(String.copyValueOf(ch, start, length));
            Log.d("SAX", "--characters()--" + nodeName + "      " + goods.getName());
        }
        else if("refercode".equals(nodeName))
        {
            goods.setRefercode(String.copyValueOf(ch, start, length));
            Log.d("SAX", "--characters()--" + nodeName + "      " + goods.getRefercode());
        }
        else if("status".equals(nodeName))
        {
            goods.setStatus(String.copyValueOf(ch, start, length));
            Log.d("SAX", "--characters()--" + nodeName + "      " + goods.getStatus());
        }
    }

    public void endElement(String uri, String localName, String qName) throws SAXException
    {
        if("task".equals(localName))
        {
            goodsList.add(goods);
            Log.d("SAX", "--endElement()--" + localName +"将数据存储到集合中");
        }
        else
        {
            Log.d("SAX", "--endElement()--" + localName);
        }

    }

    public void endDocument() throws SAXException
    {
        Log.d("SAX", "--endDocument()--");
    }


}
</pre><p><span style="font-size:18px">在解析数据的过程中在每一步都加入了日志的打印,这样通过日志我们就可以更清楚的了解到SAX解析的顺序</span></p><p></p><p><span style="font-size:18px"><img src="https://img-blog.csdn.net/20160927150032804?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /></span></p><p><span style="font-size:18px"></span></p><p><span style="font-size:18px">(2)Pull解析</span></p><p><span style="font-size:18px">       除了使用SAX解析XML文件,大家还可以使用Android内置的Pull解析器解析XML文件,Pull解析器的运行方式与SAX解析器相似,都是基于流操作文件,然后根据节点事件回调开发者编写的处理程序。因为是基于流的处理,因此Pull解析和SAX解析都比较节约内存资源,不会向DOM那样要把所有节点以对象树的形式展现在内存中,但是Pull解析比SAX解析更简明,而且不需要扫描完整个流。</span></p><p><span style="font-size:18px">      虽然Pull解析与SAX解析都是基于流操作文件,但是二者解析器的工作方式有着较大的区别。它们之间的区别为:SAX解析器的工作方式是自动将事件推入注册的事件处理器进行处理,因此你不能主动的控制事件的处理结束;而Pull解析器的工作方式则是允许你的应用程序代码主动从解析器中获取事件,正因为是主动的获取事件,因此可以通过在满足条件后不再获取事件来主动结束解析,这是它们主要的区别。</span></p><p><span style="font-size:18px">      在使用场景方面,如果在一个XML文档中我们只需要前一部分数据,但是使用SAX解析或者DOM解析对整个文档进行解析,中间不能终止解析,尽管后一部分的数据其实不需要解析,这样就造成了资源的浪费,在这种场景上使用Pull解析正合适,在进行移动开发的过程中要在每一个方面斤斤计较,想办法少做无用功,这样才能给用户更好的体验。</span></p><p><span style="font-size:18px">      Pull解析器也提供了类似于SAX解析的回调事件,开始文档START_DOCUMENT和结束文档END<span style="font-size:18px">_DOCUMENT,开始节点<span style="font-size:18px">START_TAG</span>和结束节点<span style="font-size:18px"><span style="font-size:18px">END</span><span style="font-size:18px">_TAG,遇到节点内容时需要调用nextText()方法进行提取内容。在使用Pull解析的过程中经常需要使用的类:</span></span></span></span></p><p><span style="font-size:18px"></span></p><p style="margin-top:0px; margin-bottom:5px; padding-top:0px; padding-bottom:0px; font-family:Arial; border:0px; list-style-type:none; list-style-position:initial; word-wrap:normal; word-break:normal; line-height:21px"><span style="font-size:14px">            </span><span style="font-size:18px"> XmlPullParserFactory                  工厂类,用来创建pull解析器</span></p><p style="margin-top:0px; margin-bottom:5px; padding-top:0px; padding-bottom:0px; font-family:Arial; border:0px; list-style-type:none; list-style-position:initial; word-wrap:normal; word-break:normal; line-height:21px"><span style="font-size:18px">          XmlPullParser                              Pull解析器</span></p><p style="margin-top:0px; margin-bottom:5px; padding-top:0px; padding-bottom:0px; font-family:Arial; border:0px; list-style-type:none; list-style-position:initial; word-wrap:normal; word-break:normal; line-height:21px"><span style="font-size:18px">          XmlPullParserException              异常</span></p><p style="margin-top:0px; margin-bottom:5px; padding-top:0px; padding-bottom:0px; font-family:Arial; border:0px; list-style-type:none; list-style-position:initial; word-wrap:normal; word-break:normal; line-height:21px"><span style="font-size:18px">          XmlSerializer                               该类用来将指定的对象转化为XML文档</span></p><p></p><p><span style="font-size:18px"></span></p><p><span style="font-size:18px">要进行解析的xml文件如下</span></p><p><span style="font-size:18px"></span></p><pre name="code" class="html"><?xml version="1.0" encoding="UTF-8"?>
<rt>
<tid>20140318155513001</tid>
<warehouseid>47a7377d4a9e42e3a665af0894946e21</warehouseid>
<rc>0000</rc>
<rm>成功</rm>
<tasks>
<task>
<name>102</name>
<refercode>150303101458414</refercode>
<status>3</status>
</task>
<task>
<name>119</name>
<refercode>20150303-6</refercode>
<status>3</status>
</task>
<task>
<name>202</name>
<refercode>150307125515568</refercode>
<status>3</status>
</task>
<task>
<name>107</name>
<refercode>150303140159283</refercode>
<status>3</status>
</task>
</tasks>
</rt>

相对应的Bean

public class Goods 
{
	private String name;
	private String refercode;
	private String status;
	public String getName() 
	{
		return name;
	}
	public void setName(String name) 
	{
		this.name = name;
	}
	public String getRefercode() 
	{
		return refercode;
	}
	public void setRefercode(String refercode) 
	{
		this.refercode = refercode;
	}
	public String getStatus() 
	{
		return status;
	}
	public void setStatus(String status) 
	{
		this.status = status;
	}
}


解析数据的方法

package com.example.administrator.xmlparser;

import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by ChuPeng on 2016/9/12.
 */
public class ParserXMLWithPull
{
    private static List<Goods> goodsList = null;
    private static Goods goods = null;
    private static InputStream is;
    public static List<Goods> getXmlContentForPull(String xml)
    {
        try
        {
            is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();
            xmlPullParser.setInput(is, "UTF-8");
            int eventType = xmlPullParser.getEventType();
            while (eventType != XmlPullParser.END_DOCUMENT)
            {
                String nodeName = xmlPullParser.getName();
                switch(eventType)
                {
                    case XmlPullParser.START_DOCUMENT:
                    {
                        //开始解析时的初始化
                        goodsList = new ArrayList<Goods>();
                        Log.d("PULL", "开始解析");
                    }
                    break;
                    case XmlPullParser.START_TAG:
                    {
                        if("task".equals(nodeName))
                        {
                            goods = new Goods();
                        }
                        else if("name".equals(nodeName))
                        {
                            goods.setName(xmlPullParser.nextText());
                            Log.d("PULL", "--START_TAG()--" + "name");
                        }
                        else if("refercode".equals(nodeName))
                        {
                            goods.setRefercode(xmlPullParser.nextText());
                            Log.d("PULL", "--START_TAG()--" + "refercode");
                        }
                        else if("status".equals(nodeName))
                        {
                            goods.setStatus(xmlPullParser.nextText());
                            Log.d("PULL", "--START_TAG()--" + "status");
                        }
                        else
                        {
                            Log.d("PULL", "--START_TAG()");
                        }
                    }
                    break;
                    case XmlPullParser.END_TAG:
                    {
                        if("task".equals(nodeName))
                        {
                            goodsList.add(goods);
                            Log.d("PULL", "--END_TAG()--" + "status");
                        }
                        else
                        {
                            Log.d("PULL", "--END_TAG()");
                        }
                    }
                    break;
                    case XmlPullParser.END_DOCUMENT:
                    {
                        Log.d("PULL", "解析完成");
                    }
                    break;
                    default:
                        break;
                }
                eventType = xmlPullParser.next();
            }
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
            Log.d("PULL", "UnsupportedEncodingException");
        }
        catch (XmlPullParserException e)
        {
            e.printStackTrace();
            Log.d("PULL", "XmlPullParserException");
        }
        catch (IOException e)
        {
            e.printStackTrace();
            Log.d("PULL", "IOException");
        }
        return goodsList;
    }
}

和上面的SAX解析一样,在解析数据的过程中在每一步都加入了日志的打印,以下就是使用PULL解析数据的顺序




(3)DOM解析

             最后一种常用的XML解析方法就是DOM解析,DOM解析和以上两种解析方法有所不同,它在解析XML数据的过程中是把整个XML文档当成一个对象来处理,会先把整个文档读入到内存中。由于是是基于树的结构,通常需要加载整个文档和构造DOM树,然后再开始工作。

       由于它是基于信息层次的,因而DOM被认为是基于树或者基于对象的,DOM以及广义的基于树的处理具有以上两种解析方法所不具备的灵活性。由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构做出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理,在使用上DOM解析用起来也要简单的多。但是,在使用DOM解析XML数据的过程中,整个文档必须一次性的解析完成,由于整个文档都需要载入内存,相对于SAX解析和Pull解析来说占用内存的资源较多,不适合在移动开发中使用。

       使用DOM解析的步骤


导入相关的类:

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


创建DocumentBuilder对象:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();


从文件或者流中创建Document对象:

builder = factory.newDocumentBuilder();
InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
Document doc = builder.parse(is);


提取根元素:

Element rootElement = doc.getDocumentElement();


最后再进行节点的处理


要进行解析的xml文件如下

<?xml version="1.0" encoding="UTF-8"?>
<rt>
<tid>20140318155513001</tid>
<warehouseid>47a7377d4a9e42e3a665af0894946e21</warehouseid>
<rc>0000</rc>
<rm>成功</rm>
<tasks>
<task>
<name>102</name>
<refercode>150303101458414</refercode>
<status>3</status>
</task>
<task>
<name>119</name>
<refercode>20150303-6</refercode>
<status>3</status>
</task>
<task>
<name>202</name>
<refercode>150307125515568</refercode>
<status>3</status>
</task>
<task>
<name>107</name>
<refercode>150303140159283</refercode>
<status>3</status>
</task>
</tasks>
</rt>

解析数据的方法


package com.example.administrator.xmlparser;

import android.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * Created by ChuPeng on 2016/9/12.
 */
public class ParserXMLWithDOM
{
    public static List<Goods> getXmlContentForDOM(String xml)
    {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = null;
        InputStream is = null;
        Document doc = null;
        NodeList goodsinfoProperties = null;
        String resultMessage = null;
        List<Goods> goodsList = new ArrayList<Goods>();
        try
        {
            builder = factory.newDocumentBuilder();
            is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
            doc = builder.parse(is);
            Element rootElement = doc.getDocumentElement();
            NodeList properties = rootElement.getChildNodes();
            int res = 0;
            for (int j = 0; j < properties.getLength(); j++)
            {
                Node property = properties.item(j);
                String nodeName = property.getNodeName();
                if (nodeName.equals("rc"))
                {
                    res = Integer.parseInt(property.getFirstChild().getNodeValue());
                    Log.d("DOM", nodeName + "   " + res);
                }
                else if (nodeName.equals("rm"))
                {
                    resultMessage = property.getFirstChild().getNodeValue();
                    Log.d("DOM", nodeName + "   " + resultMessage);
                }
                else if(nodeName.equals("tasks"))
                {
                    goodsinfoProperties = property.getChildNodes();
                    Log.d("DOM", nodeName);
                }
            }
            if(res == 0 && goodsinfoProperties != null)
            {

                for(int i = 0; i < goodsinfoProperties.getLength(); i++ )
                {
                    Element personElement = (Element) goodsinfoProperties.item(i);
                    NodeList childNodes = personElement.getChildNodes();
                    Goods goods = new Goods();
                    for(int j = 0; j < childNodes.getLength(); j++)
                    {
                        Node property = childNodes.item(j);
                        String nodeName = property.getNodeName();
                        if(nodeName.equals("name"))
                        {
                            if(property.getFirstChild() == null)
                            {
                                goods.setName("");
                                Log.d("DOM", nodeName + "   " + goods.getName());
                            }
                            else
                            {
                                goods.setName(property.getFirstChild().getNodeValue());
                                Log.d("DOM", nodeName + "   " + goods.getName());
                            }
                        }
                        else if(nodeName.equals("refercode"))
                        {
                            if(property.getFirstChild() == null)
                            {
                                goods.setRefercode("");
                                Log.d("DOM", nodeName + "   " + goods.getRefercode());
                            }
                            else
                            {
                                goods.setRefercode(property.getFirstChild().getNodeValue());
                                Log.d("DOM", nodeName + "   " + goods.getRefercode());
                            }
                        }
                        else if(nodeName.equals("status"))
                        {
                            if(property.getFirstChild() == null)
                            {
                                goods.setStatus("");
                                Log.d("DOM", nodeName + "   " + goods.getStatus());
                            }
                            else
                            {
                                goods.setStatus(property.getFirstChild().getNodeValue());
                                Log.d("DOM", nodeName + "   " + goods.getStatus());
                            }
                        }
                    }
                    goodsList.add(goods);
                }
            }
        }
        catch (ParserConfigurationException e)
        {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        catch (SAXException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return goodsList;
    }
}

和上面的两种解析方法一样,在解析数据的过程中在每一步都加入了日志的打印,以下就是使用DOM解析数据的顺序


4.总结

       对于以上的三种解析方法各有优缺点,在我日常的使用习惯上比较倾向于PULL解析,因为SAX解析器操作起来过于笨重,而DOM不适合文档较大的情况下使用,我通常都是进行移动端的开发,更不适合使用占用内存较大的解析方法,唯有PULL解析使用起来轻巧灵活,速度快,占用内存小。其实在解析数据的过程中需要选择适合本项目的解析方法即可。

以上Demo的源代码地址:点击打开链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值