Android ContentProvider 总结


一、ContentProvider(内容提供者)介绍

ContentProvider在android中的作用是对外共享数据, 也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据 进行添删改查。关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE或 Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。那么,这里为何要使用ContentProvider对外共享数据 呢?是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数 据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。
使用ContentProvider对外共享数据的好处是统一了数据的访问方式。

当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:

public class PersonProvider extends ContentProvider{
   public boolean onCreate()
   public Uri insert(Uri uri, ContentValues values)
   public int delete(Uri uri, String selection, String[] selectionArgs)
   public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
   public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
   public String getType(Uri uri)
}

第二步需要在AndroidManifest.xml使用对该ContentProvider进行配置,为了能让 其他应用找到该ContentProvider ,ContentProvider采用了authorities(主机名/域名)对它进行唯一标识,你可以把ContentProvider看作是一个网 站(想想,网站也是提供数据者),authorities 就是他的域名:

<manifest.... >
   <application android:icon="@drawable/icon" android:label="@string/app_name">
      <provider android:name=".provider.PersonProvider" 
           android:authorities="com.example.contentprovidertest.provider"/>
   </application>
</manifest>



二、预备知识

从Uri谈起

Uri是指通用资源标志符

A:前缀表明数据受控于一个内容提供者。它从不修改,也就是schema

B:是指在AndroidMainfest.xml中我们注册的provider中的android:authorities属性所对应的

C:具体操作于哪个条目

D:具体指定到哪个条目下的哪条记录

再看它的类结构和常用方法:

Uri

在这个里它是没有构造方法的,它通常通过下面的这个方法来返回一个Uri对象

方法名称

描述

public static Uri parse (String uriString)

通过一个传入的字符串来构造一个Uri对象


熟悉完Uri类再看与之相关的另外两个类

UriMatcher类

因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。

先看下它比较常用的几个方法:

方法名称

描述

public void addURI (String authority, String path, int code)

往UriMatcher类里添加一个拼凑的Uri,在此我们可以理解为UriMatcher为一个Uri的容器,为个容器里面包含着我们即将可能要操作的Uri,它用于我们业务逻辑的处理,特别是第三个参数code,如果通过下面的match()方法匹配成功就返回这个code值

public int match (Uri uri)

与传入的Uri匹配,它会首先与找我们之前通过addURI方法添加进来的Uri匹配,如果匹配成功就返回之前我们设置的code值,否则返回一个UriMatcher.NO_MATCH常量值为-1

熟悉完上面的方法,那么我们再来看它如何使用:

UriMatcher类用于匹配Uri,它的用法如下:

第一步把你需要匹配Uri路径全部给注册上,如下:

static {
	    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
	    
	    uriMatcher.addURI(AUTHORITY, "student" , STUDENTS);
	    uriMatcher.addURI(AUTHORITY, "student/#", STUDENT_ITEM);
	    
	    uriMatcher.addURI(AUTHORITY, "course" , COURSES);
	    uriMatcher.addURI(AUTHORITY, "course/#", COURSE_ITEM);
	}


第二步通过UriMatcher match 进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.example.contentprovidertest.provider/student/11路径,返回的匹配码为STUDENT_ITEM(2)。

int match = uriMatcher.match(uri);
		switch (match) {
		case STUDENTS:
			
			return database.query(DatabaseStruct.STUDENT.TABLE_STUDENT , projection, selection, selectionArgs, null, null, sortOrder);
		case COURSES:
			
			return database.query(DatabaseStruct.COURSE.TABLE_COURSE , projection, selection, selectionArgs, null, null, sortOrder);
		default:
			
			LogUtil.e(TAG, "Invalid query Uri "+uri);			
			break;
		}



再看另外一个工具类

ContentUris类

它用于在Uri后面追加一个ID或者解析出传入的Uri所带上的ID值,常用的两个方法如下:

方法名称

描述

public static Uri withAppendedId (Uri contentUri, long id)

用于为路径加上ID部分

public static long parseId (Uri contentUri)

从路径中获取ID部分


熟悉完上面所提及的相关的类,接下来我们再看这个ContentProvider核心类

ContentProvider

常用方法

方法名称

描述

public abstract boolean onCreate ()

在ContentProvider创建后被调用。

public abstract Uri insert (Uri uri, ContentValues values)

根据Uri插入values对就的数据

public abstract int delete (Uri uri, String selection, String[] selectionArgs)

根据Uri删除selection指定的条件所匹配的全部记录

public abstract int update (Uri uri, ContentValues values, String selection, String[] selectionArgs)

根据Uri修改selection指定的条件所匹配的全部记录

public abstract Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

根据Uri查询出selection指定的条件所匹配的全部记录,并且可以指定查询哪些列(projection),以什么方式(sortOrder)排序

public abstract String getType (Uri uri)

返回当前Uri所数据的MIME类型,如果该Uri对应的数据可能包括多条记录,那么MIME类型字符串就是以vnd.android.cursor.dir/开头,如果Uri对应的数据只包含一条记录,那么MIME类型字符串就是以vnd.android.cursor.item/开头


ContentProvider暴露了5个抽象的方法,我们需要继承ContentProvider并分别实现这5个方法即可。


既然我们知道了ContentProvider类是向外提供数据的一种机制,那么在之前我们也说过要想来操作这个对外提供的数据,我们就用到了另外一个类:

ContentResolver

在这个类里面也定义了一系列的增、删、改、查方法,与 ContentProvider定义的方法想对应。

可能大家在这里还是有点理不清这些类的一些关系,特别是ContentResolver与ContentProvider与Uri类的关系,那么我上张图吧,或许对大家有所帮助:


三、动手实践

下面通过一个demo来展示ContentProvider使用方法

1、实现ContentProvider

package com.example.contentprovidertest.provider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

import com.example.contentprovidertest.util.LogUtil;

public class YPProvider extends ContentProvider {
	private static final String TAG = YPProvider.class.getSimpleName();
	
    public static final String AUTHORITY = "com.example.contentprovidertest.provider";
    public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY);
	public static final Uri CONTENT_URI_STUDENT = Uri.parse("content://"+ AUTHORITY + "/"+DatabaseStruct.STUDENT.TABLE_STUDENT);
	public static final Uri CONTENT_URI_COURSE = Uri.parse("content://"+ AUTHORITY + "/"+DatabaseStruct.COURSE.TABLE_COURSE);
	
	private static final int STUDENTS = 1;
	private static final int STUDENT_ITEM = 2;
	
	private static final int COURSES = 10;
	private static final int COURSE_ITEM = 11;
	
	private static final UriMatcher uriMatcher;
	
	static {
	    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
	    
	    uriMatcher.addURI(AUTHORITY, "student" , STUDENTS);
	    uriMatcher.addURI(AUTHORITY, "student/#", STUDENT_ITEM);
	    
	    uriMatcher.addURI(AUTHORITY, "course" , COURSES);
	    uriMatcher.addURI(AUTHORITY, "course/#", COURSE_ITEM);
	}

	private MySqliteOpenHelper mHelper;
	
	@Override
	public boolean onCreate() {
		
		mHelper = new MySqliteOpenHelper(getContext());
		
		return false;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {

		LogUtil.i(TAG, "query uri="+uri);
		
		SQLiteDatabase database = mHelper.getReadableDatabase();
		
		int match = uriMatcher.match(uri);
		switch (match) {
		case STUDENTS:
			
			return database.query(DatabaseStruct.STUDENT.TABLE_STUDENT , projection, selection, selectionArgs, null, null, sortOrder);
		case COURSES:
			
			return database.query(DatabaseStruct.COURSE.TABLE_COURSE , projection, selection, selectionArgs, null, null, sortOrder);
		default:
			
			LogUtil.e(TAG, "Invalid query Uri "+uri);			
			break;
		}
		
		return null;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		
		LogUtil.i(TAG, "insert uri="+uri);
		
		SQLiteDatabase database = mHelper.getWritableDatabase();
		long rowId = -1;
		Uri noteUri = null;
		
		int match = uriMatcher.match(uri);
		switch (match) {
		case STUDENT_ITEM:
			
			rowId = database.insert(DatabaseStruct.STUDENT.TABLE_STUDENT , null, values);
			if (rowId == -1) {
				LogUtil.d(TAG, "couldn't insert into downloads database");
				return null;
			}
			
			noteUri=ContentUris.withAppendedId(CONTENT_URI_STUDENT, rowId);
			 //通知监听器,数据已经改变
	        getContext().getContentResolver().notifyChange(noteUri, null);
	        break;
			
		case COURSE_ITEM:
			rowId = database.insert(DatabaseStruct.COURSE.TABLE_COURSE , null, values);
			if (rowId == -1) {
				LogUtil.d(TAG, "couldn't insert into downloads database");
				return null;
			}
			
			noteUri=ContentUris.withAppendedId(CONTENT_URI_COURSE, rowId);
			 //通知监听器,数据已经改变
	        getContext().getContentResolver().notifyChange(noteUri, null);
	        
			break;
		default:
			LogUtil.e(TAG, "Invalid insert Uri "+uri);			
			break;
		}
		return noteUri;
	}
	
	//批量插入
	@Override
	public int bulkInsert(Uri uri, ContentValues[] values) {
		
		LogUtil.e(TAG, "bulkInsert Uri "+uri);
		
		SQLiteDatabase database = mHelper.getWritableDatabase();
		int numInserted = -1;
		
		database.beginTransaction();
		
		int match = uriMatcher.match(uri);
		switch (match) {
		case STUDENT_ITEM:
			
			try {
				for (ContentValues cv : values) {
				    long newID = database.insertOrThrow(DatabaseStruct.STUDENT.TABLE_STUDENT, null, cv);
				    if (newID <= 0) {
				        throw new SQLException("Failed to insert row into " + uri);
				    }
				}
				
				database.setTransactionSuccessful();
				 //通知监听器,数据已经改变
				getContext().getContentResolver().notifyChange(CONTENT_URI_STUDENT, null);
				
				numInserted = values.length;
				
			}finally{
				database.endTransaction();	//提交事务
			}
	        
			break;
		case COURSE_ITEM:
			
			try {
				for (ContentValues cv : values) {
				    long newID = database.insertOrThrow(DatabaseStruct.COURSE.TABLE_COURSE, null, cv);
				    if (newID <= 0) {
				        throw new SQLException("Failed to insert row into " + uri);
				    }
				}
				
				database.setTransactionSuccessful();
				//通知监听器,数据已经改变
				getContext().getContentResolver().notifyChange(CONTENT_URI_COURSE, null);
				
				numInserted = values.length;
				
			}finally{
				database.endTransaction();	//提交事务
			}
	        
			break;
		default:
			LogUtil.e(TAG, "Invalid bulkInsert Uri "+uri);		
			break;
		}
		
		return numInserted;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		
		LogUtil.i(TAG, "delete uri="+uri);
		
		SQLiteDatabase database = mHelper.getWritableDatabase();
		int rowId = -1;
		Uri noteUri = null;
		
		int match = uriMatcher.match(uri);
		switch (match) {
		case STUDENT_ITEM:
			
			rowId = database.delete(DatabaseStruct.STUDENT.TABLE_STUDENT , selection, selectionArgs);
			if (rowId == -1) {
				LogUtil.d(TAG, "couldn't delete "+uri);
				return -1;
			}
			
			noteUri=ContentUris.withAppendedId(CONTENT_URI_STUDENT, rowId);
			//通知监听器,数据已经改变
	        getContext().getContentResolver().notifyChange(noteUri, null);
	        break;
		case COURSE_ITEM:
			
			rowId = database.delete(DatabaseStruct.COURSE.TABLE_COURSE , selection, selectionArgs);
			if (rowId == -1) {
				LogUtil.d(TAG, "couldn't delete "+uri);
				return -1;
			}
			
			noteUri=ContentUris.withAppendedId(CONTENT_URI_COURSE, rowId);
			//通知监听器,数据已经改变
	        getContext().getContentResolver().notifyChange(noteUri, null);
	        break;
		default:
			LogUtil.e(TAG, "Invalid delete Uri "+uri);		
			break;
		}
		return rowId;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		
		LogUtil.i(TAG, "update uri="+uri);
		
		SQLiteDatabase database = mHelper.getWritableDatabase();
		int rowId = -1;
		Uri noteUri= null;
		
		int match = uriMatcher.match(uri);
		switch (match) {
		case STUDENT_ITEM:
			
			rowId = database.update(DatabaseStruct.STUDENT.TABLE_STUDENT , values, selection, selectionArgs);
			if (rowId == -1) {
				LogUtil.d(TAG, "couldn't update "+uri);
			}
			
			noteUri=ContentUris.withAppendedId(CONTENT_URI_STUDENT, rowId);
	        getContext().getContentResolver().notifyChange(noteUri, null);
	        break;
		case COURSE_ITEM:
			
			rowId = database.update(DatabaseStruct.COURSE.TABLE_COURSE , values, selection, selectionArgs);
			if (rowId == -1) {
				LogUtil.d(TAG, "couldn't update "+uri);
			}
			
			noteUri=ContentUris.withAppendedId(CONTENT_URI_COURSE, rowId);
	        getContext().getContentResolver().notifyChange(noteUri, null);
	        break;
		default:
			LogUtil.e(TAG, "Invalid update Uri "+uri);		
			break;
		}
		return rowId;
	}
	
	@Override
	public String getType(Uri uri) {
		
		LogUtil.i(TAG, "getType uri="+uri);
		
		int match = uriMatcher.match(uri);
		
		String type = null;
		switch (match) {
		case STUDENTS:
			type = "marks";
			break;
		case COURSES:
			type = "downloads";
			break;
		default:
			break;
		}
		return type;
	}
}


其中 bulkInsert 方法 为批量插入方法,它是非抽象的,不需要这个功能的可以不用实现。


2、Android Manifest 文件中 注册ContentProvider

<provider
            android:name=".provider.YPProvider"
            android:authorities="com.example.contentprovidertest.provider" android:exported="false">
        </provider>


3、使用ContentResolver 操作数据库


package com.example.contentprovidertest;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.ContentValues;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

import com.example.contentprovidertest.entity.Course;
import com.example.contentprovidertest.entity.Student;
import com.example.contentprovidertest.provider.DatabaseStruct;
import com.example.contentprovidertest.provider.YPProvider;
import com.example.contentprovidertest.util.LogUtil;

public class MainActivity extends Activity implements OnClickListener {

	private static final String TAG = MainActivity.class.getSimpleName();
	
	private EditText et_inputA;
	private EditText et_inputB;
	private ListView studentListView;
	private ListView courseListView;
	private SimpleDataAdapter courseAdapter;
	private SimpleDataAdapter stAdapter;

	private MyContentObserver mObserver;
	private List<Student> stuList = new ArrayList<Student>();
	private List<Course> courseList = new ArrayList<Course>();

	private Handler mHandler = new Handler(){
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case 1:
				
				stAdapter.notifyDataSetChanged();
				break;
			case 2:
				
				courseAdapter.notifyDataSetChanged();
				break;
			default:
				break;
			}
		};
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		findView();
	}

	private void findView() {

		Button bt_add_student = (Button) findViewById(R.id.bt_add_student);
		Button bt_delete_student = (Button) findViewById(R.id.bt_delete_student);

		Button bt_add_course = (Button) findViewById(R.id.bt_add_course);
		Button bt_delete_course = (Button) findViewById(R.id.bt_delete_course);

		et_inputA = (EditText) findViewById(R.id.et_inputA);
		et_inputB = (EditText) findViewById(R.id.et_inputB);

		studentListView = (ListView) findViewById(R.id.student_List);
		courseListView = (ListView) findViewById(R.id.course_List);

		courseAdapter = new SimpleDataAdapter(this, courseList);
		courseListView.setAdapter(courseAdapter);
		
		stAdapter = new SimpleDataAdapter(MainActivity.this, stuList);
		studentListView.setAdapter(stAdapter);
		
		
		bt_add_student.setOnClickListener(this);
		bt_delete_student.setOnClickListener(this);
		bt_add_course.setOnClickListener(this);
		bt_delete_course.setOnClickListener(this);
		
		mObserver = new MyContentObserver();
		getContentResolver().registerContentObserver(YPProvider.CONTENT_URI, true, mObserver);
		
		
		queryTableCourse();
		queryTableStudent();
		initListView(true);
	}
	
	@Override
	protected void onStop() {
		
		getContentResolver().unregisterContentObserver(mObserver);
		
		super.onStop();
	}
	
	private class MyContentObserver extends ContentObserver{

		public MyContentObserver() {
			super(new Handler());
		}

		@Override
		public void onChange(boolean selfChange) {
			
			LogUtil.e(TAG, "database changed");
			
			queryTableCourse();
			queryTableStudent();
			
			super.onChange(selfChange);
		}
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.bt_add_student:

			addTable(true);
			break;
		case R.id.bt_delete_student:
			deleteTable(true);
			break;

		case R.id.bt_add_course:
			addTable(false);
			break;
		case R.id.bt_delete_course:
			deleteTable(false);
			break;
		default:
			break;
		}
		
		et_inputA.setText("");
		et_inputB.setText("");
	}

	/**
	 * 查询 Student表
	 */
	private void queryTableStudent() {

		Cursor cursor = getContentResolver().query(
				YPProvider.CONTENT_URI_STUDENT,
				new String[] { DatabaseStruct.STUDENT._ID,
						DatabaseStruct.STUDENT.NAME,
						DatabaseStruct.STUDENT.GRADE }, null, null, null);
		
		List<Student> list = new ArrayList<Student>();
		
		while(cursor!=null && cursor.moveToNext()){
			
			int id = cursor.getInt(0);
			String name = cursor.getString(1);
			int grade = cursor.getInt(2);
			
//			LogUtil.i(TAG, "id="+id+",name="+name+",grade="+grade);
			
			Student s = new Student();
			s.setId(id);
			s.setName(name);
			s.setGrade(grade);
			
			list.add(s);
		}
		if(cursor!=null){
			cursor.close();	//关闭cursor
		}
		
		stuList.clear();
		stuList.addAll(list);
		list = null;
		
		mHandler.sendEmptyMessage(1);
	}

	/**
	 * 查询 Course表
	 */
	private void queryTableCourse() {

		Cursor cursor = getContentResolver().query(
				YPProvider.CONTENT_URI_COURSE,
				new String[] { DatabaseStruct.COURSE._ID,
						DatabaseStruct.COURSE.NAME,
						DatabaseStruct.COURSE.TEACHER}, null, null, null);
		
		List<Course> list = new ArrayList<Course>();
		
		while(cursor!=null && cursor.moveToNext()){
			
			int id = cursor.getInt(0);
			String name = cursor.getString(1);
			String teacher = cursor.getString(2);
			
//			LogUtil.i(TAG, "id="+id+",name="+name+",teacher="+teacher);
			
			Course c = new Course();
			c.setId(id);
			c.setName(name);
			c.setTeacher(teacher);
			
			list.add(c);
		}
		if(cursor!=null){
			cursor.close();	//关闭cursor
		}
		
		courseList.clear();
		courseList.addAll(list);
		list = null;
		mHandler.sendEmptyMessage(2);
	}

	/**
	 * 根据 id 删除 Student/Course表中的某一条数据
	 * @param isStudent
	 */
	private void deleteTable(boolean isStudent) {

		String sa = et_inputA.getText().toString();
		if(sa==null || "".equals(sa)){
			
			return ;
		}
		
		try {
			int id = Integer.parseInt(sa);
		} catch (NumberFormatException e) {
			e.printStackTrace();
			Toast.makeText(getApplicationContext(), "ID格式非法,请输入要删除数据的ID", Toast.LENGTH_SHORT).show();
			return;
		}
		
		if (isStudent) {
			Uri uri = Uri.parse(YPProvider.CONTENT_URI_STUDENT + "/3");
			getContentResolver().delete(uri, DatabaseStruct.STUDENT._ID+" = ?",
					new String[] { sa });
		} else {
			Uri uri = Uri.parse(YPProvider.CONTENT_URI + "/3");
			getContentResolver().delete(uri, DatabaseStruct.COURSE._ID+" = ?",
					new String[] { sa });
		}
		
		initListView(isStudent);
	}

	/**
	 * 向 Student/Course表中 插入一条数据
	 * @param isStudent
	 */
	private void addTable(boolean isStudent) {
		
		String sa = et_inputA.getText().toString();
		String sb = et_inputB.getText().toString();
		
		if(sa==null || sb==null || "".equals(sa) || "".equals(sb)){
			Toast.makeText(getApplicationContext(), "请输入内容", Toast.LENGTH_LONG).show();
			return ;
		}
		
		if (isStudent) {
			int grade = 0;
			try {
				grade = Integer.parseInt(sb);
			} catch (NumberFormatException e) {
				e.printStackTrace();
				Toast.makeText(getApplicationContext(), "成绩格式非法,请输入整数", Toast.LENGTH_SHORT).show();
				return;
			}
			
			ContentValues values = new ContentValues();
			values.put(DatabaseStruct.STUDENT.NAME, sa);
			values.put(DatabaseStruct.STUDENT.GRADE, grade);

			Uri uri = Uri.parse(YPProvider.CONTENT_URI_STUDENT + "/1");
			getContentResolver().insert(uri, values);
		} else {
			ContentValues values = new ContentValues();
			values.put(DatabaseStruct.COURSE.NAME, sa);
			values.put(DatabaseStruct.COURSE.TEACHER,sb);

			Uri uri = Uri.parse(YPProvider.CONTENT_URI_COURSE + "/1");
			getContentResolver().insert(uri, values);
		}
		
		initListView(isStudent);
	}

	private void initListView(boolean isStudent) {
		
		if(isStudent){
			studentListView.setVisibility(View.VISIBLE);
			courseListView.setVisibility(View.GONE);
		}else{
			studentListView.setVisibility(View.GONE);
			courseListView.setVisibility(View.VISIBLE);
		}
	}
}




工程代码下载:http://download.csdn.net/detail/fx_sky/6414595





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值