Android数据存储

在Android中,可供选择的存储方式有SharedPreferences、文件存储、SQLite数据库方式、内容提供器(Content provider)和网络。

一.SharedPreferences方式

        Android提供用来存储一些简单的配置信息的一种机制,例如,一些默认欢迎语、登录的用户名和密码等。其以键值对的方式存储,

使得我们可以很方便的读取和存入.

       1)程序要实现的功能:
       我们在Name文本框中输入wangwu,在Password文本框中输入123456,然后退出这个应用。我们在应用程序列表中找到这个应用,重新启动,可以看到其使用了前面输入的Name和Password

       2)实现的代码

       布局

        view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    > 
    <TextView    
        android:layout_width="fill_parent"   
        android:layout_height="wrap_content"   
        android:text="SharedPreferences demo" 
    />         
    <EditText 
        android:id="@+id/name" 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="" 
        > 
    </EditText>  
       
    <EditText 
        android:id="@+id/password" 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:password="true" 
        android:text="" 
        > 
    </EditText> 
</LinearLayout>  
 

        主要实现代码

        view plaincopy to clipboardprint?
package com.demo;  
 
import android.app.Activity;  
import android.content.SharedPreferences;  
import android.os.Bundle;  
import android.widget.EditText;  
 
public class SharedPreferencesDemo extends Activity {  
      
    public static final String SETTING_INFOS = "SETTING_Infos";   
    public static final String NAME = "NAME";   
    public static final String PASSWORD = "PASSWORD";   
    private EditText field_name;  //接收用户名的组件  
    private EditText filed_pass;  //接收密码的组件  
 
    @Override 
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
          
        //Find VIew   
        field_name = (EditText) findViewById(R.id.name);  //首先获取用来输入用户名的组件  
        filed_pass = (EditText) findViewById(R.id.password); //同时也需要获取输入密码的组件  
 
        // Restore preferences  
        SharedPreferences settings = getSharedPreferences(SETTING_INFOS, 0); //获取一个SharedPreferences对象  
        String name = settings.getString(NAME, "");  //取出保存的NAME  
        String password = settings.getString(PASSWORD, ""); //取出保存的PASSWORD  
 
        //Set value  
        field_name.setText(name);  //将取出来的用户名赋予field_name  
        filed_pass.setText(password);  //将取出来的密码赋予filed_pass  
    }  
      
    @Override   
    protected void onStop(){   
        super.onStop();   
          
        SharedPreferences settings = getSharedPreferences(SETTING_INFOS, 0); //首先获取一个SharedPreferences对象  
        settings.edit()   
                .putString(NAME, field_name.getText().toString())   
                .putString(PASSWORD, filed_pass.getText().toString())   
                .commit();   
    } //将用户名和密码保存进去  
 
}  
    
       SharedPreferences保存到哪里去了?

       SharedPreferences是以XML的格式以文件的方式自动保存的,在DDMS中的File Explorer中展开到/data/data/<package

name>/shared_prefs下,以上面这个为例,可以看到一个叫做SETTING_Infos.xml的文件

       注意:Preferences只能在同一个包内使用,不能在不同的包之间使用。

二.文件存储方式

       在Android中,其提供了openFileInput 和 openFileOuput 方法读取设备上的文件,下面看个例子代码,具体如下所示:
             String FILE_NAME = "tempfile.tmp";  //确定要操作文件的文件名
             FileOutputStream fos = openFileOutput(FILE_NAME, Context.MODE_PRIVATE); //初始化
             FileInputStream fis = openFileInput(FILE_NAME); //创建写入流

       上述代码中两个方法只支持读取该应用目录下的文件,读取非其自身目录下的文件将会抛出异常。需要提醒的是,如果调用

FileOutputStream 时指定的文件不存在,Android 会自动创建它。另外,在默认情况下,写入的时候会覆盖原文件内容,如果想把

新写入的内容附加到原文件内容后,则可以指定其模式为Context.MODE_APPEND。

三.SQLite数据库方式

      SQLite是Android所带的一个标准的数据库,它支持SQL语句,它是一个轻量级的嵌入式数据库。
      1)实现的功能

          在这个例子里边,我们在程序的主界面有一些按钮,通过这些按钮可以对数据库进行标准的增、删、改、查。

      2)实现代码

          所用到的字符文件

          view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?> 
<resources> 
    <string name="app_name">SQLite数据库操作实例</string> 
    <string name="button1">建立数据库表</string> 
    <string name="button2">删除数据库表</string> 
    <string name="button3">插入两条记录</string> 
    <string name="button4">删除一条记录</string> 
    <string name="button5">查看数据库表</string> 
</resources>   

          布局代码

          view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    > 
      
    <TextView    
        android:layout_width="fill_parent"   
        android:layout_height="wrap_content"   
        android:text="@string/app_name" 
    /> 
    <Button   
        android:text="@string/button1"   
        android:id="@+id/button1"   
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content" 
        ></Button> 
    <Button   
        android:text="@string/button2"   
        android:id="@+id/button2"   
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content" 
        ></Button> 
    <Button   
        android:text="@string/button3"   
        android:id="@+id/button3"   
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content" 
        ></Button> 
    <Button   
        android:text="@string/button4"   
        android:id="@+id/button4"   
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content" 
        ></Button> 
    <Button   
        android:text="@string/button5"   
        android:id="@+id/button5"   
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content" 
        ></Button> 
 
</LinearLayout>  
 

          主要代码

          view plaincopy to clipboardprint?
package com.sqlite;  
 
import android.app.Activity;  
import android.content.Context;  
import android.database.Cursor;  
import android.database.SQLException;  
import android.database.sqlite.SQLiteDatabase;  
import android.database.sqlite.SQLiteOpenHelper;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Button;  
 
/* 
 * 什么是SQLiteDatabase? 
 * 一个SQLiteDatabase的实例代表了一个SQLite的数据库,通过SQLiteDatabase实例的一些方法,我们可以执行SQL语句, 
 * 对数据库进行增、删、查、改的操作。需要注意的是,数据库对于一个应用来说是私有的,并且在一个应用当中,数据库的名字也是惟一的。  
 */ 
 
/* 
 * 什么是SQLiteOpenHelper ?  
 * 这个类主要生成一个数据库,并对数据库的版本进行管理。 
 * 当在程序当中调用这个类的方法getWritableDatabase()或者getReadableDatabase()方法的时候,如果当时没有数据,那么Android系统就会自动生成一个数据库。 
 * SQLiteOpenHelper 是一个抽象类,我们通常需要继承它,并且实现里边的3个函数, 
 *     onCreate(SQLiteDatabase):在数据库第一次生成的时候会调用这个方法,一般我们在这个方法里边生成数据库表。 
 *     onUpgrade(SQLiteDatabase, int, int):当数据库需要升级的时候,Android系统会主动的调用这个方法。一般我们在这个方法里边删除数据表,并建立新的数据表,当然是否还需要做其他的操作,完全取决于应用的需求。 
 *     onOpen(SQLiteDatabase):这是当打开数据库时的回调函数,一般也不会用到。  
 */ 
 
public class SQLiteDemo extends Activity {  
      
    OnClickListener listener1 = null;  
    OnClickListener listener2 = null;  
    OnClickListener listener3 = null;  
    OnClickListener listener4 = null;  
    OnClickListener listener5 = null;  
 
    Button button1;  
    Button button2;  
    Button button3;  
    Button button4;  
    Button button5;  
 
    DatabaseHelper mOpenHelper;  
 
    private static final String DATABASE_NAME = "dbForTest.db";  
    private static final int DATABASE_VERSION = 1;  
    private static final String TABLE_NAME = "diary";  
    private static final String TITLE = "title";  
    private static final String BODY = "body";  
 
    //建立一个内部类,主要生成一个数据库  
    private static class DatabaseHelper extends SQLiteOpenHelper {  
          
        DatabaseHelper(Context context) {  
            super(context, DATABASE_NAME, null, DATABASE_VERSION);  
        }  
 
        //在数据库第一次生成的时候会调用这个方法,一般我们在这个方法里边生成数据库表。  
        @Override 
        public void onCreate(SQLiteDatabase db) {  
 
            String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE  
                    + " text not null, " + BODY + " text not null " + ");";  
            Log.i("haiyang:createDB=", sql);  
            db.execSQL(sql);  
        }  
 
        @Override 
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
        }  
    }  
 
    @Override 
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        prepareListener();  
        initLayout();  
        mOpenHelper = new DatabaseHelper(this);  
 
    }  
 
    private void initLayout() {  
        button1 = (Button) findViewById(R.id.button1);  
        button1.setOnClickListener(listener1);  
 
        button2 = (Button) findViewById(R.id.button2);  
        button2.setOnClickListener(listener2);  
 
        button3 = (Button) findViewById(R.id.button3);  
        button3.setOnClickListener(listener3);  
          
        button4 = (Button) findViewById(R.id.button4);  
        button4.setOnClickListener(listener4);  
 
        button5 = (Button) findViewById(R.id.button5);  
        button5.setOnClickListener(listener5);  
 
    }  
 
    private void prepareListener() {  
        listener1 = new OnClickListener() {  
            public void onClick(View v) {  
                CreateTable();  
            }  
        };  
        listener2 = new OnClickListener() {  
            public void onClick(View v) {  
                dropTable();  
            }  
        };  
        listener3 = new OnClickListener() {  
            public void onClick(View v) {  
                insertItem();  
            }  
        };  
        listener4 = new OnClickListener() {  
            public void onClick(View v) {  
                deleteItem();  
            }  
        };  
        listener5 = new OnClickListener() {  
            public void onClick(View v) {  
                showItems();  
            }  
        };  
    }  
 
    /* 
     * 重新建立数据表 
     */ 
    private void CreateTable() {  
        //mOpenHelper.getWritableDatabase()语句负责得到一个可写的SQLite数据库,如果这个数据库还没有建立,  
        //那么mOpenHelper辅助类负责建立这个数据库。如果数据库已经建立,那么直接返回一个可写的数据库。  
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
        String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE  
                + " text not null, " + BODY + " text not null " + ");";  
        Log.i("haiyang:createDB=", sql);  
 
        try {  
            db.execSQL("DROP TABLE IF EXISTS diary");  
            db.execSQL(sql);  
            setTitle("数据表成功重建");  
        } catch (SQLException e) {  
            setTitle("数据表重建错误");  
        }  
    }  
 
    /* 
     * 删除数据表 
     */ 
    private void dropTable() {  
        //mOpenHelper.getWritableDatabase()语句负责得到一个可写的SQLite数据库,如果这个数据库还没有建立,  
        //那么mOpenHelper辅助类负责建立这个数据库。如果数据库已经建立,那么直接返回一个可写的数据库。  
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
        String sql = "drop table " + TABLE_NAME;  
        try {  
            db.execSQL(sql);  
            setTitle("数据表成功删除:" + sql);  
        } catch (SQLException e) {  
            setTitle("数据表删除错误");  
        }  
    }  
 
    /* 
     * 插入两条数据 
     */ 
    private void insertItem() {  
        //mOpenHelper.getWritableDatabase()语句负责得到一个可写的SQLite数据库,如果这个数据库还没有建立,  
        //那么mOpenHelper辅助类负责建立这个数据库。如果数据库已经建立,那么直接返回一个可写的数据库。  
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
        String sql1 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY  
                + ") values('haiyang', 'android的发展真是迅速啊');";  
        String sql2 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY  
                + ") values('icesky', 'android的发展真是迅速啊');";  
        try {  
            // Log.i()会将参数内容打印到日志当中,并且打印级别是Info级别  
            // Android支持5种打印级别,分别是Verbose、Debug、Info、Warning、Error,当然我们在程序当中一般用到的是Info级别  
            Log.i("haiyang:sql1=", sql1);  
            Log.i("haiyang:sql2=", sql2);  
            db.execSQL(sql1);  
            db.execSQL(sql2);  
            setTitle("插入两条数据成功");  
        } catch (SQLException e) {  
            setTitle("插入两条数据失败");  
        }  
    }  
 
    /* 
     * 删除其中的一条数据 
     */ 
    private void deleteItem() {  
        try {  
            //mOpenHelper.getWritableDatabase()语句负责得到一个可写的SQLite数据库,如果这个数据库还没有建立,  
            //那么mOpenHelper辅助类负责建立这个数据库。如果数据库已经建立,那么直接返回一个可写的数据库。  
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
            //第一个参数是数据库表名,在这里是TABLE_NAME,也就是diary。   
            //第二个参数,相当于SQL语句当中的where部分,也就是描述了删除的条件。  
            //如果在第二个参数当中有“?”符号,那么第三个参数中的字符串会依次替换在第二个参数当中出现的“?”符号。   
            db.delete(TABLE_NAME, " title = 'haiyang'", null);  
            setTitle("删除title为haiyang的一条记录");  
        } catch (SQLException e) {  
 
        }  
 
    }  
 
    /* 
     * 在屏幕的title区域显示当前数据表当中的数据的条数。 
     */ 
    /* 
     * Cursor cur = db.query(TABLE_NAME, col, null, null, null, null, null)语句将查询到的数据放到一个Cursor 当中。 
        这个Cursor里边封装了这个数据表TABLE_NAME当中的所有条列。  
        query()方法相当的有用,在这里我们简单地讲一下。 
            第一个参数是数据库里边表的名字,比如在我们这个例子,表的名字就是TABLE_NAME,也就是"diary"。 
            第二个字段是我们想要返回数据包含的列的信息。在这个例子当中我们想要得到的列有title、body。我们把这两个列的名字放到字符串数组里边来。 
            第三个参数为selection,相当于SQL语句的where部分,如果想返回所有的数据,那么就直接置为null。 
            第四个参数为selectionArgs。在selection部分,你有可能用到“?”,那么在selectionArgs定义的字符串会代替selection中的“?”。 
            第五个参数为groupBy。定义查询出来的数据是否分组,如果为null则说明不用分组。 
            第六个参数为having ,相当于SQL语句当中的having部分。 
            第七个参数为orderBy,来描述我们期望的返回值是否需要排序,如果设置为null则说明不需要排序。 
     */ 
      
    private void showItems() {  
 
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();  
        String col[] = { TITLE, BODY };  
        //查询数据  
        Cursor cur = db.query(TABLE_NAME, col, null, null, null, null, null);  
        Integer num = cur.getCount();  
        setTitle(Integer.toString(num) + " 条记录");  
    }  

 

四.内容提供器(Content provider)方式

      在Android的设计“哲学”里是鼓励开发者使用内部类的,这样不但使用方便,而且执行效率也高。

      1.什么是ContentProvider
      数据在Android当中是私有的,当然这些数据包括文件数据和数据库数据以及一些其他类型的数据。难道两个程序之间就没有办法对于数据进行交换?解决这个问题主要靠ContentProvider。
 一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去。外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据,当然,中间也会涉及一些权限的问题。
      下边列举一些较常见的接口,这些接口如下所示。
      query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。
      insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。
      update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。
      delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。

      2.什么是ContentResolver
      外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,在Activity当中通过getContentResolver()可以得到当前应用的ContentResolver实例。
      ContentResolver提供的接口和ContentProvider中需要实现的接口对应,主要有以下几个。
      query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。
      insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。
      update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。
      delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。

      3.ContentProvider和ContentResolver中用到的Uri
      在ContentProvider和ContentResolver当中用到了Uri的形式通常有两种,一种是指定全部数据,另一种是指定某个ID的数据。
     我们看下面的例子。
          content://contacts/people/  这个Uri指定的就是全部的联系人数据。
          content://contacts/people/1 这个Uri指定的是ID为1的联系人的数据。 

     在上边两个类中用到的Uri一般由3部分组成。
         第一部分是:"content://" 。
         第二部分是要获得数据的一个字符串片段。
        最后就是ID(如果没有指定ID,那么表示返回全部)。

     由于URI通常比较长,而且有时候容易出错,且难以理解。所以,在Android当中定义了一些辅助类,并且定义了一些常量来代替这些长字符串的使用,例如下边的代码:
     Contacts.People.CONTENT_URI (联系人的URI)。
     1)实现的功能

         在这个例子里边,首先在系统的联系人应用当中插入一些联系人信息,然后把这些联系人的名字和电话再显示出来

     2)实现方法

     view plaincopy to clipboardprint?

package com.contentProvider;

import android.app.ListActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts.Phones;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;

public class ContentProviderDemo extends ListActivity {
   
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //getContentResolver()方法得到应用的ContentResolver实例。
        // query(Phones.CONTENT_URI, null, null, null, null)。它是ContentResolver里的方法,负责查询所有联系人,并返回一个Cursor。这个方法参数比较多,每个参数的具体含义如下。
        //·  第一个参数为Uri,在这个例子里边这个Uri是联系人的Uri。
        //·  第二个参数是一个字符串的数组,数组里边的每一个字符串都是数据表中某一列的名字,它指定返回数据表中那些列的值。
        //·  第三个参数相当于SQL语句的where部分,描述哪些值是我们需要的。
        //·  第四个参数是一个字符串数组,它里边的值依次代替在第三个参数中出现的“?”符号。
        //·  第五个参数指定了排序的方式。
        Cursor c = getContentResolver().query(Phones.CONTENT_URI, null, null, null, null);
        startManagingCursor(c); //让系统来管理生成的Cursor。
        ListAdapter adapter = new SimpleCursorAdapter(
          this,
                android.R.layout.simple_list_item_2,
                c,
                new String[] { Phones.NAME, Phones.NUMBER },
                new int[] { android.R.id.text1, android.R.id.text2 });
        setListAdapter(adapter); //将ListView和SimpleCursorAdapter进行绑定。
    }
   
}

五. 网络存储方式

 1.例子介绍
         通过邮政编码查询该地区的天气预报,以POST发送的方式发送请求到webservicex.net站点,访问WebService.webservicex.net站点上提供查询天气预报的服务,具体信息请参考其WSDL文档,网址为:
http://www.webservicex.net/WeatherForecast.asmx?WSDL
输入:美国某个城市的邮政编码。
输出:该邮政编码对应城市的天气预报。
2.实现步骤如下
(1)如果需要访问外部网络,则需要在AndroidManifest.xml文件中加入如下代码申请权限许可:
<!-- Permissions -->
<uses-permission Android:name="Android.permission.INTERNET" />
(2)以HTTP POST的方式发送(注意:SERVER_URL并不是指WSDL的URL,而是服务本身的URL)。实现的代码如下所示:
private static final String SERVER_URL = "http://www.webservicex.net/WeatherForecast. asmx/GetWeatherByZipCode"; //定义需要获取的内容来源地址
HttpPost request = new HttpPost(SERVER_URL); //根据内容来源地址创建一个Http请求
// 添加一个变量
List <NameValuePair> params = new ArrayList <NameValuePair>();
// 设置一个华盛顿区号
params.add(new BasicNameValuePair("ZipCode", "200120"));  //添加必须的参数
request.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); //设置参数的编码
try {
HttpResponse httpResponse = new DefaultHttpClient().execute(request); //发送请求并获取反馈
// 解析返回的内容
if(httpResponse.getStatusLine().getStatusCode() != 404) 
{
  String result = EntityUtils.toString(httpResponse.getEntity());
  Log.d(LOG_TAG, result);
}
} catch (Exception e) {
Log.e(LOG_TAG, e.getMessage());
}
 代码解释:
如上代码使用Http从webservicex获取ZipCode为“200120”(美国WASHINGTON D.C)的内容,其返回的内容如下:
<WeatherForecasts xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http: //www.w3.org/2001/XMLSchema-instance" xmlns="http://www.webservicex.net">
  <Latitude>38.97571</Latitude>
  <Longitude>77.02825</Longitude>
  <AllocationFactor>0.024849</AllocationFactor>
  <FipsCode>11</FipsCode>
  <PlaceName>WASHINGTON</PlaceName>
  <StateCode>DC</StateCode>
  <Details>
    <WeatherData>
      <Day>Saturday, April 25, 2009</Day>
      <WeatherImage>http://forecast.weather.gov/images/wtf/sct.jpg</WeatherImage>
      <MaxTemperatureF>88</MaxTemperatureF>
      <MinTemperatureF>57</MinTemperatureF>
      <MaxTemperatureC>31</MaxTemperatureC>
      <MinTemperatureC>14</MinTemperatureC>
    </WeatherData>
    <WeatherData>
      <Day>Sunday, April 26, 2009</Day>
      <WeatherImage>http://forecast.weather.gov/images/wtf/few.jpg</WeatherImage>
      <MaxTemperatureF>89</MaxTemperatureF>
      <MinTemperatureF>60</MinTemperatureF>
      <MaxTemperatureC>32</MaxTemperatureC>
      <MinTemperatureC>16</MinTemperatureC>
    </WeatherData>

  </Details>
</WeatherForecasts>

转载自:Android开发入门与实践

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值