android开发笔记之ContentProvider使用样例一

1.说明:

     ContentProvider作为android的四大组件,主要是用来保存一些复杂的数据,并且db数据各个app应用都可以读取,操作。这个例子,主要是在db数据库中新建一个表,并提供正常的增,删,改,查操作和使用AsyncQueryHandler来异步对db数据库的数据进行增,删,改,查操作,以减少操作的时间,提高效率!

2.效果图:


3.详细代码:

(1)PersonInfo.java

package com.example.testcontentprovider01;

public class PersonInfo {  
    public String name;  
    public String gender;  
    public int age; 
    
    public PersonInfo(String name,String gender,int age){
    	this.name = name;
    	this.gender = gender;
    	this.age = age;
    }
    
    public String getName(){
    	return name;
    }
    
    public void setName(String name){
    	this.name = name;
    }
    
    public String getGender(){
    	return gender;
    }
    
    public void setGender(String gender){
    	this.gender = gender;
    }
     
    public int getAge(){
    	return age;
    }   
    public void setAge(int age){
    	this.age = age;
    }

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return super.toString()+"--name:"+name+"--gender:"+gender+"--age:"+age;
	}
    
} 

(2)DatabaseHelper.java

package com.example.testcontentprovider01;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {
	
    private static final String DATABASE_NAME = "db_demo.db";  
    private static final int DATABASE_VERSION = 1; 
    
    public DatabaseHelper(Context context){
    	super(context, DATABASE_NAME, null, DATABASE_VERSION); 
    }

	@Override
	public void onCreate(SQLiteDatabase db) {
		// TODO Auto-generated method stub
        db.execSQL("CREATE TABLE " + Provider.Person.TABLE_NAME + " ("  
                + Provider.Person._ID + " INTEGER PRIMARY KEY,"  
                + Provider.Person.NAME + " TEXT,"
                + Provider.Person.GENDER + " TEXT,"
                + Provider.Person.AGE + " INTEGER"  
                + ");");  
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// TODO Auto-generated method stub
		db.execSQL("DROP TABLE IF EXISTS " + Provider.Person.TABLE_NAME);  
        onCreate(db); 
	}
	
}

(3)Provider.java

package com.example.testcontentprovider01;

import android.net.Uri;
import android.provider.BaseColumns;

public class Provider{
    
    public static final String AUTHORITY = "com.android.provider.demo.person";       
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.android.demo"; 
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.android.demo";  
  
    public static final class Person implements BaseColumns {          
        public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY +"/persons");  
        public static final String TABLE_NAME = "table_person";  
        public static final String DEFAULT_SORT_ORDER = "age desc";   
        
        public static final String NAME = "name";
        public static final String GENDER = "gender";
        public static final String AGE = "age";           
    }  
}

(4)PersonProvider.java

package com.example.testcontentprovider01;

import java.util.HashMap;
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.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;

public class PersonProvider extends ContentProvider{
      
    private static final int PERSONS = 1;  
    private static final int PERSONS_ID = 2;  
    private static  UriMatcher sUriMatcher;  
    private DatabaseHelper mOpenHelper;  
    private static HashMap<String, String> sPersonsProjectionMap;  
    
    static {  
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);     
        sUriMatcher.addURI(Provider.AUTHORITY, "persons", PERSONS);  
        sUriMatcher.addURI(Provider.AUTHORITY, "persons/#", PERSONS_ID);   
        
        sPersonsProjectionMap = new HashMap<String, String>();  
        sPersonsProjectionMap.put(Provider.Person._ID, Provider.Person._ID);  
        sPersonsProjectionMap.put(Provider.Person.NAME, Provider.Person.NAME);
        sPersonsProjectionMap.put(Provider.Person.GENDER, Provider.Person.GENDER);
        sPersonsProjectionMap.put(Provider.Person.AGE, Provider.Person.AGE);  
    }  
   
	@Override
	public boolean onCreate() {
		// TODO Auto-generated method stub		
		mOpenHelper = new DatabaseHelper(getContext());  
        return true;  
	}
	
	@Override
	public String getType(Uri uri) {
		// TODO Auto-generated method stub
		switch (sUriMatcher.match(uri)) {  
        case PERSONS:  
            return Provider.CONTENT_TYPE;  
        case PERSONS_ID:  
            return Provider.CONTENT_ITEM_TYPE;  
        default:  
            throw new IllegalArgumentException("Unknown URI " + uri);  
        }  
	}
	
	@Override
	public Uri insert(Uri uri, ContentValues initialValues) {
		// TODO Auto-generated method stub
		// Validate the requested uri  
        if (sUriMatcher.match(uri) != PERSONS) {  
            throw new IllegalArgumentException("Unknown URI " + uri);  
        }  
  
        ContentValues values;  
        if (initialValues != null) {  
            values = new ContentValues(initialValues);  
        } else {  
            values = new ContentValues();  
        }  
  
        // Make sure that the fields are all set  
        if (values.containsKey(Provider.Person.NAME) == false) {  
            values.put(Provider.Person.NAME, "");  
        }  
        
        if (values.containsKey(Provider.Person.GENDER) == false) {  
            values.put(Provider.Person.GENDER, "");  
        } 
  
        if (values.containsKey(Provider.Person.AGE) == false) {  
            values.put(Provider.Person.AGE, 0);  
        }  
  
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
        long rowId = db.insert(Provider.Person.TABLE_NAME, Provider.Person.NAME, values);  
        if (rowId > 0) {  
            Uri noteUri = ContentUris.withAppendedId(Provider.Person.CONTENT_URI, rowId);  
            getContext().getContentResolver().notifyChange(noteUri, null);  
            return noteUri;  
        }  
        throw new SQLException("Failed to insert row into " + uri);  
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
			String sortOrder) {
		// TODO Auto-generated method stub
		SQLiteQueryBuilder qb = new SQLiteQueryBuilder();  
        qb.setTables(Provider.Person.TABLE_NAME);  
  
        switch (sUriMatcher.match(uri)) {  
        case PERSONS:  
            qb.setProjectionMap(sPersonsProjectionMap);  
            break;   
        case PERSONS_ID:  
            qb.setProjectionMap(sPersonsProjectionMap);  
            qb.appendWhere(Provider.Person._ID + "=" + uri.getPathSegments().get(1));  
            break;   
        default:  
            throw new IllegalArgumentException("Unknown URI " + uri);  
        }  
  
        // If no sort order is specified use the default  
        String orderBy;  
        if (TextUtils.isEmpty(sortOrder)) {  
            orderBy = Provider.Person.DEFAULT_SORT_ORDER;  
        } else {  
            orderBy = sortOrder;  
        }  
  
        // Get the database and run the query  
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();  
        Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);  
  
        // Tell the cursor what uri to watch, so it knows when its source data changes  
        c.setNotificationUri(getContext().getContentResolver(), uri);  
        return c;  
	}

	@Override
	public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
		// TODO Auto-generated method stub
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
        int count;  
        switch (sUriMatcher.match(uri)) {  
        case PERSONS:  
            count = db.update(Provider.Person.TABLE_NAME, values, where, whereArgs);  
            break;  
        case PERSONS_ID:  
            String noteId = uri.getPathSegments().get(1);  
            count = db.update(Provider.Person.TABLE_NAME, values, Provider.Person._ID + "=" + noteId  
                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);  
            break;    
        default:  
            throw new IllegalArgumentException("Unknown URI " + uri);  
        }  
        getContext().getContentResolver().notifyChange(uri, null);  
        return count;  
	}
	
	@Override
	public int delete(Uri uri, String where, String[] whereArgs) {
		// TODO Auto-generated method stub
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
        int count;  
        switch (sUriMatcher.match(uri)) {  
        case PERSONS:  
            count = db.delete(Provider.Person.TABLE_NAME, where, whereArgs);  
            break;    
        case PERSONS_ID:  
            String noteId = uri.getPathSegments().get(1);  
            count = db.delete(Provider.Person.TABLE_NAME, Provider.Person._ID + "=" + noteId  
                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);  
            break;  
        default:  
            throw new IllegalArgumentException("Unknown URI " + uri);  
        }  
        getContext().getContentResolver().notifyChange(uri, null);  
        return count;  
	}
}
(5)MainActivity.java

package com.example.testcontentprovider01;

import com.example.testcontentprovider01.Provider.Person;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentValues;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener{
			
	private Button myInserButton = null;
	private Button myDeleButton = null;
	private Button myUpdateButton = null;
	private Button myQueryButton = null;	
	private Button myAsyInserButton = null;
	private Button myAsyDeleButton = null;
	private Button myAsyUpdateButton = null;
	private Button myAsyQueryButton = null;	
	
	private Controller myController = null;
	private PersonInfo myPerson = null;	
	private int i = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);       
        init();
    }

    private void init() {
		// TODO Auto-generated method stub
    	myInserButton = (Button)findViewById(R.id.myInserButton);
    	myInserButton.setOnClickListener(this);
    	myDeleButton = (Button)findViewById(R.id.myDeleButton);
    	myDeleButton.setOnClickListener(this);
    	myUpdateButton = (Button)findViewById(R.id.myUpdateButton);
    	myUpdateButton.setOnClickListener(this);
    	myQueryButton = (Button)findViewById(R.id.myQueryButton);
    	myQueryButton.setOnClickListener(this);  
    	myAsyInserButton = (Button)findViewById(R.id.myAsyInserButton);
    	myAsyInserButton.setOnClickListener(this);
    	myAsyDeleButton = (Button)findViewById(R.id.myAsyDeleButton);
    	myAsyDeleButton.setOnClickListener(this);
    	myAsyUpdateButton = (Button)findViewById(R.id.myAsyUpdateButton);
    	myAsyUpdateButton.setOnClickListener(this);
    	myAsyQueryButton = (Button)findViewById(R.id.myAsyQueryButton);
    	myAsyQueryButton.setOnClickListener(this);     	
    		
    	myController = new Controller(this);   	
	}
    
	@Override
	public void onClick(View view) {
		// TODO Auto-generated method stub
		if(view == myInserButton){
			Log.i(Controller.TAG, "onClick--myInserButton"); 		
			myPerson = new PersonInfo("test_name"+"_"+i,"man",i++);
			myController.onInSertDateLister(myPerson);			
		}else if(view == myDeleButton){
			Log.i(Controller.TAG, "onClick--myDeleButton");			
			String[] deleteValue = {"test_name_0"};  
            String where = "name";  
			myController.onDeleteDateLister(where,deleteValue);			
		}else if(view == myUpdateButton){
			Log.i(Controller.TAG, "onClick--myUpdateButton");			
			ContentValues values = new ContentValues();  
            values.put(Person.NAME, "testUpdate"); 
            values.put(Person.GENDER, "woman"); 
            values.put(Person.AGE,39);                          
            String where = "name";  
            String[] selectValue = {"test_name_0"};    
			myController.onUpdateDateLister(values,where,selectValue);			
		}else if(view == myQueryButton){
			Log.i(Controller.TAG, "onClick--myQueryButton");			
			myController.onQueryDateLister();			
		}else if(view == myAsyInserButton){
			Log.i(Controller.TAG, "onClick--myAsyInserButton");	
			myPerson = new PersonInfo("asy_test_name"+"_"+i,"man",i++);
			myController.onAsyncInsertDataLister(myPerson);				
		}else if(view == myAsyDeleButton){
			Log.i(Controller.TAG, "onClick--myAsyDeleButton");			
			String[] deleteValue = {"asy_test_name_0"};  
            String where = "name";  
			myController.onAsyncDeleteDataLister(where, deleteValue);			
		}else if(view == myAsyUpdateButton){
			Log.i(Controller.TAG, "onClick--myAsyUpdateButton");			
			ContentValues values = new ContentValues();  
            values.put(Person.NAME, "asyTestUpdate"); 
            values.put(Person.GENDER, "asy_woman"); 
            values.put(Person.AGE,39);                          
            String where = "name";  
            String[] selectValue = {"asy_test_name_0"};    
			myController.onAsyncUpdateDataLister(values,where,selectValue);		
		}else if(view == myAsyQueryButton){
			Log.i(Controller.TAG, "onClick--myAsyQueryButton");			
			myController.onAsyncQueryDataLister();		
		}		
	}   
}

(6)Controller.java

package com.example.testcontentprovider01;

import com.example.testcontentprovider01.Provider.Person;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

public class Controller{
	public static final String TAG = "TestContentProvider01";  
		   	
	private Context myContext = null;
	private AsyncQueryHandler asyncQuery = null;
	
    private String[] PERSON_PROJECTION = new String[] {   
    		Person._ID,  // 0  
    		Person.NAME, // 1 
    		Person.GENDER,//2
    		Person.AGE  // 3 
            }; 
		
	public Controller(Context myContext){
		this.myContext = myContext;	
		this.asyncQuery = new MyAsyncQueryHandler(myContext.getContentResolver()); 
	}
	
	private class MyAsyncQueryHandler extends AsyncQueryHandler {

		public MyAsyncQueryHandler(ContentResolver cr) {
			super(cr);
			// TODO Auto-generated constructor stub
		}

		@Override
		protected void onDeleteComplete(int token, Object cookie, int result) {
			// TODO Auto-generated method stub
			Log.i(TAG, "MyAsyncQueryHandler--onDeleteComplete");   
			super.onDeleteComplete(token, cookie, result);		
		}

		@Override
		protected void onInsertComplete(int token, Object cookie, Uri uri) {
			// TODO Auto-generated method stub
			Log.i(TAG, "MyAsyncQueryHandler--onInsertComplete");   
			super.onInsertComplete(token, cookie, uri);
		}

		@Override
		protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
			// TODO Auto-generated method stub
			Log.i(TAG, "MyAsyncQueryHandler--onQueryComplete"); 
			super.onQueryComplete(token, cookie, cursor);			
			if (cursor != null && cursor.getCount() > 0) {      
				for (int i = 0; i < cursor.getCount(); i++) {  	                    
					cursor.moveToPosition(i);    
					String name = cursor.getString(1);    
					String gender = cursor.getString(2);    
					int age = cursor.getInt(3);  	           
					Log.i(TAG, "db第"+i+"个数据:"+"--name:"+name+"--gender:"+gender+"--age:"+age);     
				}    
				cursor.close();  
			}    
		}    
		
		@Override
		protected void onUpdateComplete(int token, Object cookie, int result) {
			// TODO Auto-generated method stub
			Log.i(TAG, "MyAsyncQueryHandler--onUpdateComplete");
			super.onUpdateComplete(token, cookie, result);
		}		
	}
	
    public void onAsyncQueryDataLister() {  
        // TODO Auto-generated method stub  
    	Log.i(TAG, "Controller--onAsyncQueryDataLister");  
        asyncQuery.startQuery(0, null, Person.CONTENT_URI, PERSON_PROJECTION, null, null,Person.DEFAULT_SORT_ORDER);  
    } 
    
    public void onAsyncInsertDataLister(PersonInfo myPerson) {  
        // TODO Auto-generated method stub  
    	Log.i(TAG, "Controller--onAsyncInsertDataLister");  
		ContentValues values = new ContentValues();  
        values.put(Person.NAME, myPerson.name);
        values.put(Person.GENDER, myPerson.gender);
        values.put(Person.AGE, myPerson.age);
        asyncQuery.startInsert(0, null, Person.CONTENT_URI, values);
    }
    
    public void onAsyncDeleteDataLister(String where, String[] deleteValue) {  
        // TODO Auto-generated method stub  
    	Log.i(TAG, "Controller--onAsyncDeleteDataLister");
        asyncQuery.startDelete(0, null, Person.CONTENT_URI, where+"=?", deleteValue);      
    }
    
    public void onAsyncUpdateDataLister(ContentValues values, String where, String[] selectValue) {  
        // TODO Auto-generated method stub   
    	Log.i(TAG, "Controller--onAsyncUpdateDataLister");
        asyncQuery.startUpdate(0, null, Person.CONTENT_URI, values, where+"=?", selectValue);       
    }
    
	public void onInSertDateLister(PersonInfo myPerson) {
		// TODO Auto-generated method stub
		ContentValues values = new ContentValues();  
        values.put(Person.NAME, myPerson.name);
        values.put(Person.GENDER, myPerson.gender);
        values.put(Person.AGE, myPerson.age);  
        Uri uri = myContext.getContentResolver().insert(Person.CONTENT_URI, values);  
     
        String lastPath = uri.getLastPathSegment();  
        if (TextUtils.isEmpty(lastPath)) {  
            Log.i(TAG, "insert failure!");  
        } else {  
            Log.i(TAG, "insert success! the id is:" + lastPath);  
        }        
	}

	public void onDeleteDateLister(String where, String[] deleteValue) {
		// TODO Auto-generated method stub
		// 删除ID为1的记录的方法: 
        //Uri uri = ContentUris.withAppendedId(Person.CONTENT_URI, 1); 
        //myContext.getContentResolver().delete(uri, null, null); 
                          
		myContext.getContentResolver().delete(Person.CONTENT_URI, where+"=?", deleteValue); 
		Log.i(TAG, "delete data:"+"--where:"+where+"--deleteValue:"+deleteValue);  
	}

	public void onUpdateDateLister(ContentValues values, String where, String[] selectValue) {
		// TODO Auto-generated method stub
	
        // 更新ID为1的记录 
        //Uri uri = ContentUris.withAppendedId(Person.CONTENT_URI, 1);         
        //myContext.getContentResolver().update(uri, values, null, null); 
        
  
        //getContentResolver().update(uri, values, "name"+"=?", selectValue);  
		myContext.getContentResolver().update(Person.CONTENT_URI, values, where+"=?", selectValue);  
	}

	public void onQueryDateLister() {
		// TODO Auto-generated method stub 
		Cursor cursor = myContext.getContentResolver().query(Person.CONTENT_URI,
				PERSON_PROJECTION, null, null,Person.DEFAULT_SORT_ORDER);		
		if (cursor.moveToFirst()) {  
			for (int i = 0; i < cursor.getCount(); i++) {  
				cursor.moveToPosition(i);  
				String name = cursor.getString(1); 
				String gender = cursor.getString(2); 
				int age = cursor.getInt(3);
				Log.i(TAG, "db第"+i+"个数据:"+"--name:"+name+"--gender:"+gender+"--age:"+age);  
			}  
		}  
		cursor.close();  
	}
}

(7)activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ScrollView
		android:layout_width="match_parent"
    	android:layout_height="match_parent">
    	<RelativeLayout 
			android:layout_width="match_parent"
    		android:layout_height="match_parent"
    	    > 
			<Button
		        android:id="@+id/myInserButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="正常的插入数据" />
		    
			<Button
		        android:id="@+id/myDeleButton"
		        android:layout_below="@id/myInserButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="正常的删除数据" />
		        
			<Button
		        android:id="@+id/myUpdateButton"
		        android:layout_below="@id/myDeleButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="正常的更新数据" />
			
			<Button
		        android:id="@+id/myQueryButton"
		        android:layout_below="@id/myUpdateButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="正常的查询数据" />
			
			
			<Button
		        android:id="@+id/myAsyInserButton"
		        android:layout_below="@id/myQueryButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="异步的插入数据" />
		    
			<Button
		        android:id="@+id/myAsyDeleButton"
		        android:layout_below="@id/myAsyInserButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="异步的删除数据" />
		        
			<Button
		        android:id="@+id/myAsyUpdateButton"
		        android:layout_below="@id/myAsyDeleButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="异步的更新数据" />
			
			<Button
		        android:id="@+id/myAsyQueryButton"
		        android:layout_below="@id/myAsyUpdateButton"
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:text="异步的查询数据" />   
	        
		</RelativeLayout>
    </ScrollView>   	
</RelativeLayout>

(8)androidmanifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testcontentprovider01"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.testcontentprovider01.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
                
        <provider android:name=".PersonProvider"  
            android:authorities="com.android.provider.demo.person" /> 
            
    </application>

</manifest>

4.代码下载地址:

http://download.csdn.net/detail/hfreeman2008/7808907

5.参考资料:

1.android 玩转ContentProvider之一--实现ContentProvider操作数据库

http://blog.csdn.net/maylian7700/article/details/7365368

2.android 玩转ContentProvider之三--实现一个ContentProvider对多张表进行操作

http://blog.csdn.net/maylian7700/article/details/7365433

3.ContentProvider-----一个完整的样例(一)

http://blog.csdn.net/hfreeman2011/article/details/8556445

4.Android 异步开发之 AsyncQueryHandler

http://blog.csdn.net/hfreeman2011/article/details/8555474


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hfreeman2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值