Android之ContentProvider总结

1.适用场景

1) ContentProvider为存储和读取数据提供了统一的接口

2) 使用ContentProvider,应用程序可以实现数据共享

3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)

2.相关概念介绍

1)ContentProvider简介
       当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。

2)Uri类简介

      Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")

      在Content Provider中使用的查询字符串有别于标准的SQL查询。很多诸如select, add, delete, modify等操作我们都使用一种特殊的URI来进行,这种URI由3个部分组成, “content://”, 代表数据的路径,和一个可选的标识数据的ID。以下是一些示例URI:

     content://media/internal/images  这个URI将返回设备上存储的所有图片
     content://contacts/people/  这个URI将返回设备上的所有联系人信息
     content://contacts/people/45 这个URI返回单个结果(联系人信息中ID为45的联系人记录)

  尽管这种查询字符串格式很常见,但是它看起来还是有点令人迷惑。为此,Android提供一系列的帮助类(在android.provider包下),里面包含了很多以类变量形式给出的查询字符串,这种方式更容易让我们理解一点,因此,如上面content://contacts/people/45这个URI就可以写成如下形式:

  Uri person = ContentUris.withAppendedId(People.CONTENT_URI,  45);

然后执行数据查询:

Cursor cur = managedQuery(person, null, null, null);

这个查询返回一个包含所有数据字段的游标,我们可以通过迭代这个游标来获取所有的数据:

复制代码
package com.wissen.testApp;
public class ContentProviderDemo extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       displayRecords();
    }

    private void displayRecords() {
        //该数组中包含了所有要返回的字段
     String columns[] = new String[] { People.NAME, People.NUMBER };
       Uri mContacts = People.CONTENT_URI;
       Cursor cur = managedQuery(
          mContacts,
          columns,  // 要返回的数据字段
         null,          // WHERE子句
         null,         // WHERE 子句的参数
         null         // Order-by子句
     );
       if (cur.moveToFirst()) {
           String name = null;
           String phoneNo = null;
           do {
              // 获取字段的值
            name = cur.getString(cur.getColumnIndex(People.NAME));
             phoneNo = cur.getString(cur.getColumnIndex(People.NUMBER));
             Toast.makeText(this, name + ” ” + phoneNo, Toast.LENGTH_LONG).show();
          } while (cur.moveToNext());
       }
    }
}
复制代码

 

  上例示范了一个如何依次读取联系人信息表中的指定数据列name和number。

修改记录:
我们可以使用ContentResolver.update()方法来修改数据,我们来写一个修改数据的方法:

private void updateRecord(int recNo, String name) {
    Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, recNo);
    ContentValues values = new ContentValues();
    values.put(People.NAME, name);
    getContentResolver().update(uri, values, null, null);
}

现在你可以调用上面的方法来更新指定记录:

updateRecord(10, ”XYZ”);   //更改第10条记录的name字段值为“XYZ”

添加记录:
要增加记录,我们可以调用ContentResolver.insert()方法,该方法接受一个要增加的记录的目标URI,以及一个包含了新记录值的Map对象,调用后的返回值是新记录的URI,包含记录号。
上面的例子中我们都是基于联系人信息簿这个标准的Content Provider,现在我们继续来创建一个insertRecord() 方法以对联系人信息簿中进行数据的添加:

复制代码
private void insertRecords(String name, String phoneNo) {
    ContentValues values = new ContentValues();
    values.put(People.NAME, name);
    Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
    Log.d(”ANDROID”, uri.toString());
    Uri numberUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
    values.clear();
    values.put(Contacts.Phones.TYPE, People.Phones.TYPE_MOBILE);
    values.put(People.NUMBER, phoneNo);
    getContentResolver().insert(numberUri, values);
}
复制代码

这样我们就可以调用insertRecords(name, phoneNo)的方式来向联系人信息簿中添加联系人姓名和电话号码。


删除记录:
Content Provider中的getContextResolver.delete()方法可以用来删除记录,下面的记录用来删除设备上所有的联系人信息:

private void deleteRecords() {
    Uri uri = People.CONTENT_URI;
    getContentResolver().delete(uri, null, null);
}

你也可以指定WHERE条件语句来删除特定的记录:

getContentResolver().delete(uri, “NAME=” + “‘XYZ XYZ’”, null);

这将会删除name为‘XYZ XYZ’的记录。

3. 创建ContentProvider

要创建我们自己的Content Provider的话,我们需要遵循以下几步:
a. 创建一个继承了ContentProvider父类的类

b. 定义一个名为CONTENT_URI,并且是public static final的Uri类型的类变量,你必须为其指定一个唯一的字符串值,最好的方案是以类的全名称, 如:
public static final Uri CONTENT_URI = Uri.parse( “content://com.google.android.MyContentProvider”);

c. 定义你要返回给客户端的数据列名。如果你正在使用Android数据库,必须为其定义一个叫_id的列,它用来表示每条记录的唯一性。

d. 创建你的数据存储系统。大多数Content Provider使用Android文件系统或SQLite数据库来保持数据,但是你也可以以任何你想要的方式来存储。

e. 如果你要存储字节型数据,比如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,通过它来读取对应的文件数据。处理这种数据类型的Content Provider需要实现一个名为_data的字段,_data字段列出了该文件在Android文件系统上的精确路径。这个字段不仅是供客户端使用,而且也可以供ContentResolver使用。客户端可以调用ContentResolver.openOutputStream()方法来处理该URI指向的文件资源;如果是ContentResolver本身的话,由于其持有的权限比客户端要高,所以它能直接访问该数据文件。

f. 声明public static String型的变量,用于指定要从游标处返回的数据列。

g. 查询返回一个Cursor类型的对象。所有执行写操作的方法如insert(), update() 以及delete()都将被监听。我们可以通过使用ContentResover().notifyChange()方法来通知监听器关于数据更新的信息。

h. 在AndroidMenifest.xml中使用<provider>标签来设置Content Provider。

i. 如果你要处理的数据类型是一种比较新的类型,你就必须先定义一个新的MIME类型,以供ContentProvider.geType(url)来返回。MIME类型有两种形式:一种是为指定的单个记录的,还有一种是为多条记录的。这里给出一种常用的格式:

  vnd.android.cursor.item/vnd.yourcompanyname.contenttype (单个记录的MIME类型)
  比如, 一个请求列车信息的URI如content://com.example.transportationprovider/trains/122 可能就会返回typevnd.android.cursor.item/vnd.example.rail这样一个MIME类型。

  vnd.android.cursor.dir/vnd.yourcompanyname.contenttype (多个记录的MIME类型)
  比如, 一个请求所有列车信息的URI如content://com.example.transportationprovider/trains 可能就会返回vnd.android.cursor.dir/vnd.example.rail这样一个MIME 类型。

下列代码将创建一个Content Provider,它仅仅是存储用户名称并显示所有的用户名称(使用 SQLLite数据库存储这些数据):

复制代码
public class MyUsers {
    public static final String AUTHORITY  = “com.wissen.MyContentProvider”;

    // BaseColumn类中已经包含了 _id字段
   public static final class User implements BaseColumns {
        public static final Uri CONTENT_URI  = Uri.parse(”content://com.wissen.MyContentProvider”);
        // 表数据列
        public static final String  USER_NAME  = “USER_NAME”;
    }
}
复制代码

上面的类中定义了Content Provider的CONTENT_URI,以及数据列。下面我们将定义基于上面的类来定义实际的Content Provider类:

复制代码
public class MyContentProvider extends ContentProvider {
    private SQLiteDatabase     sqlDB;
    private DatabaseHelper    dbHelper;
    private static final String  DATABASE_NAME = “Users.db”;
    private static final int  DATABASE_VERSION= 1;
    private static final String TABLE_NAME= “User”;
    private static final String TAG = “MyContentProvider”;

    private static class DatabaseHelper extends SQLiteOpenHelper {
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            //创建用于存储数据的表
        db.execSQL(”Create table ” + TABLE_NAME + “( _id INTEGER PRIMARY KEY AUTOINCREMENT, USER_NAME TEXT);”);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL(”DROP TABLE IF EXISTS ” + TABLE_NAME);
            onCreate(db);
        }
    }

    @Override
    public int delete(Uri uri, String s, String[] as) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues contentvalues) {
        sqlDB = dbHelper.getWritableDatabase();
        long rowId = sqlDB.insert(TABLE_NAME, “”, contentvalues);
        if (rowId > 0) {
            Uri rowUri = ContentUris.appendId(MyUsers.User.CONTENT_URI.buildUpon(), rowId).build();
            getContext().getContentResolver().notifyChange(rowUri, null);
            return rowUri;
        }
        throw new SQLException(”Failed to insert row into ” + uri);
    }

    @Override
    public boolean onCreate() {
        dbHelper = new DatabaseHelper(getContext());
        return (dbHelper == null) ? false : true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        qb.setTables(TABLE_NAME);
        Cursor c = qb.query(db, projection, selection, null, null, null, sortOrder);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }

    @Override
    public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
        return 0;
    }
}
复制代码

一个名为MyContentProvider的Content Provider创建完成了,它用于从Sqlite数据库中添加和读取记录。

Content Provider的入口需要在AndroidManifest.xml中配置:

<provider android:name=”MyContentProvider” android:authorities=”com.wissen.MyContentProvider” />

之后,让我们来使用这个定义好的Content Provider:

1)为应用程序添加ContentProvider的访问权限。

2)通过getContentResolver()方法得到ContentResolver对象。

3)调用ContentResolver类的query()方法查询数据,该方法会返回一个Cursor对象。

4)对得到的Cursor对象进行分析,得到需要的数据。

5)调用Cursor类的close()方法将Cursor对象关闭。


复制代码
public class MyContentDemo extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        insertRecord(”MyUser”);
        displayRecords();
    }
   
    private void insertRecord(String userName) {
        ContentValues values = new ContentValues();
        values.put(MyUsers.User.USER_NAME, userName);
        getContentResolver().insert(MyUsers.User.CONTENT_URI, values);
    }

    private void displayRecords() {
        String columns[] = new String[] { MyUsers.User._ID, MyUsers.User.USER_NAME };
        Uri myUri = MyUsers.User.CONTENT_URI;
        Cursor cur = managedQuery(myUri, columns,null, null, null );
        if (cur.moveToFirst()) {
            String id = null;
            String userName = null;
            do {
                id = cur.getString(cur.getColumnIndex(MyUsers.User._ID));
                userName = cur.getString(cur.getColumnIndex(MyUsers.User.USER_NAME));
                Toast.makeText(this, id + ” ” + userName, Toast.LENGTH_LONG).show();
           } while (cur.moveToNext());
       }
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
应用场景:

在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite。但是我们知道一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享,而且这些知识在前面我都有介绍,有时候我们需要操作其他应用程序的一些数据,例如我们需要操作系统里的媒体库、通讯录等,这时我们就可能通过ContentProvider来满足我们的需求了

ContentProvider概述:

ContentProvider向我们提供了我们在应用程序之前共享数据的一种机制,而我们知道每一个应用程序都是运行在不同的应用程序的,数据和文件在不同应用程序之间达到数据的共享不是没有可能,而是显得比较复杂,而正好Android中的ContentProvider则达到了这一需求,比如有时候我们需要操作手机里的联系人,手机里的多媒体等一些信息,我们都可以用到这个ContentProvider来达到我们所需。

如何理解ContentProvider

上面说了一大堆ContentProvider的概述,可能大家还是不太特别理解ContentProvider到底是干什么的,那么我们以一个网站来形象的描述这个ContentProvider吧,可以这么理解为ContentProvider就是一个网站,它向我们去访问网站这里的数据达到了一种可能,它就是一个向外提供数据的接口。那么既然它是向外提供数据,我们有时候也需要去修改数据,这时我们就可以用到另外一个类来实现这个对数据的修改ContentResolver类,这个类就可以通过URI来操作数据。至于这些类的作用及描述在下面就会一一的介绍到。

如何实现ContentProvider

理解了ContentProvider类,那么我们怎么去实现ContentProvider呢?怎么样让外部程序去访问或者修改我们的数据呢?这样的一个操作其实是非常简单的,我们只需要下面的两步就可以实现ContentProvider

1、  编写一个实现ContentProvider的在,这个子类必须要实现一些必须实现的方法,在ContentProvider类里面,有一系列针对于数据的增、删、改、查等方法

2、  ContentProvider也是Android中的四大组件,因此也必须在AndroidMainfest.xml中完成对ContentProvider的注册。注册方式为:

 

与ContentProvider相关操作的类介绍

从Uri谈起

什么是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,它的用法如下:

UriMatcher类的用法

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

//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码

UriMatcher  sMatcher = new UriMatcher(UriMatcher.NO_MATCH);

//如果match()方法匹配content:// com.jiahui.provider.myprovider/person路径,返回匹配码为1

sMatcher.addURI(“com.jiahui.provider.myprovider”, “person”, 1);//添加需要匹配uri,如果匹配就会返回匹配码

//如果match()方法匹配content:// com.jiahui.provider.myprovider /person/230路径,返回匹配码为2

sMatcher.addURI(“com.jiahui.provider.myprovider”, “person/#”, 2);//#号为通配符

switch (sMatcher.match(Uri.parse("content:// com.jiahui.provider.myprovider /person/10"))) {

   case 1

    break;

   case 2

    break;

   default://不匹配

    break;

}

注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://cn.itcast.provider.personprovider/person路径,返回的匹配码为1

再看另外一个工具类

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类是向外提供数据的一种机制,那么在之前我们也说过要想来操作这个对外提供的数据,我们就用到了另外一个类:

ContentResolver

在这个类里面也定义了一系列的增、删、改、查方法,与其ContentProvider定义的方法基本上相同,在此不再复核。读者可以自己查阅相关文档。

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

好了熟悉完上面所述的这么多类那么我们就在实践中见证真理吧:

实例:

实现效果:

  

代码实现:

 

先开发我们自己的ContentProvider:

[java]  view plain copy print ?
  1. package com.jiahui.provider;  
  2.   
  3. import com.jiahui.db.DBHelper;  
  4.   
  5. import android.content.ContentProvider;  
  6. import android.content.ContentUris;  
  7. import android.content.ContentValues;  
  8. import android.content.UriMatcher;  
  9. import android.database.Cursor;  
  10. import android.database.sqlite.SQLiteDatabase;  
  11. import android.net.Uri;  
  12.   
  13. public class MyProvider extends ContentProvider {  
  14.   
  15.     private DBHelper dbHelper;  
  16.     // 定义一个UriMatcher类  
  17.     private static final UriMatcher MATCHER = new UriMatcher(  
  18.             UriMatcher.NO_MATCH);  
  19.     private static final int PERSONS = 1;  
  20.     private static final int PERSON = 2;  
  21.     static {  
  22.         MATCHER.addURI("com.jiahui.provider.myprovider""person", PERSONS);  
  23.         MATCHER.addURI("com.jiahui.provider.myprovider""person/#", PERSON);  
  24.   
  25.     }  
  26.     @Override  
  27.     public boolean onCreate() {  
  28.         System.out.println("---oncreate----");  
  29.         dbHelper = new DBHelper(this.getContext());  
  30.         return false;  
  31.     }  
  32.   
  33.     // 查询数据  
  34.     @Override  
  35.     public Cursor query(Uri uri, String[] projection, String selection,  
  36.             String[] selectionArgs, String sortOrder) {  
  37.   
  38.         SQLiteDatabase db = dbHelper.getWritableDatabase();  
  39.         switch (MATCHER.match(uri)) {  
  40.         case PERSONS:  
  41.             // 查询所有的数据  
  42.             return db.query("person", projection, selection, selectionArgs,  
  43.                     nullnull, sortOrder);  
  44.   
  45.         case PERSON:  
  46.             // 查询某个ID的数据  
  47.             // 通过ContentUris这个工具类解释出ID  
  48.             long id = ContentUris.parseId(uri);  
  49.             String where = " _id=" + id;  
  50.             if (!"".equals(selection) && selection != null) {  
  51.                 where = selection + " and " + where;  
  52.   
  53.             }  
  54.   
  55.             return db.query("person", projection, where, selectionArgs, null,  
  56.                     null, sortOrder);  
  57.         default:  
  58.   
  59.             throw new IllegalArgumentException("unknow uri" + uri.toString());  
  60.         }  
  61.   
  62.     }  
  63.   
  64.     // 返回当前操作的数据的mimeType  
  65.     @Override  
  66.     public String getType(Uri uri) {  
  67.         switch (MATCHER.match(uri)) {  
  68.         case PERSONS:  
  69.             return "vnd.android.cursor.dir/person";  
  70.         case PERSON:  
  71.             return "vnd.android.cursor.item/person";  
  72.         default:  
  73.             throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
  74.         }  
  75.     }  
  76.   
  77.     // 插入数据  
  78.     @Override  
  79.     public Uri insert(Uri uri, ContentValues values) {  
  80.         SQLiteDatabase db = dbHelper.getWritableDatabase();  
  81.         Uri insertUri = null;  
  82.         switch (MATCHER.match(uri)) {  
  83.         case PERSONS:  
  84.   
  85.             long rowid = db.insert("person""name", values);  
  86.             insertUri = ContentUris.withAppendedId(uri, rowid);  
  87.   
  88.             break;  
  89.   
  90.         default:  
  91.             throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
  92.         }  
  93.         return insertUri;  
  94.     }  
  95.   
  96.     // 删除数据  
  97.     @Override  
  98.     public int delete(Uri uri, String selection, String[] selectionArgs) {  
  99.         SQLiteDatabase db = dbHelper.getWritableDatabase();  
  100.         int count = 0;  
  101.         switch (MATCHER.match(uri)) {  
  102.         case PERSONS:  
  103.             count = db.delete("person", selection, selectionArgs);  
  104.             return count;  
  105.   
  106.         case PERSON:  
  107.             long id = ContentUris.parseId(uri);  
  108.             String where = "_id=" + id;  
  109.             if (selection != null && !"".equals(selection)) {  
  110.                 where = selection + " and " + where;  
  111.             }  
  112.             count = db.delete("person", where, selectionArgs);  
  113.             return count;  
  114.   
  115.         default:  
  116.             throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
  117.         }  
  118.     }  
  119.   
  120.     // 更新数据  
  121.     @Override  
  122.     public int update(Uri uri, ContentValues values, String selection,  
  123.             String[] selectionArgs) {  
  124.   
  125.         SQLiteDatabase db = dbHelper.getWritableDatabase();  
  126.   
  127.         int count = 0;  
  128.         switch (MATCHER.match(uri)) {  
  129.         case PERSONS:  
  130.             count = db.update("person", values, selection, selectionArgs);  
  131.             break;  
  132.         case PERSON:  
  133.             // 通过ContentUri工具类得到ID  
  134.             long id = ContentUris.parseId(uri);  
  135.             String where = "_id=" + id;  
  136.             if (selection != null && !"".equals(selection)) {  
  137.                 where = selection + " and " + where;  
  138.             }  
  139.             count = db.update("person", values, where, selectionArgs);  
  140.             break;  
  141.         default:  
  142.             throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
  143.         }  
  144.         return count;  
  145.     }  
  146.   
  147. }  

 

千万别忘记了要在AndroidMainfest.xml文件中注册这个组件哦:

[html]  view plain copy print ?
  1. <provider  
  2.            android:authorities="com.jiahui.provider.myprovider"  
  3.            android:name=".MyProvider" >  
  4.        </provider>  

 

然后在一个主Activity编写一些实现代码:

[java]  view plain copy print ?
  1. package com.jiahui.provider;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7.   
  8. import android.app.Activity;  
  9. import android.content.ContentResolver;  
  10. import android.content.ContentUris;  
  11. import android.content.ContentValues;  
  12. import android.content.Context;  
  13. import android.content.Intent;  
  14. import android.database.Cursor;  
  15. import android.net.Uri;  
  16. import android.os.Bundle;  
  17. import android.os.Handler;  
  18. import android.os.Message;  
  19. import android.view.View;  
  20. import android.widget.AdapterView;  
  21. import android.widget.AdapterView.OnItemClickListener;  
  22. import android.widget.Button;  
  23. import android.widget.EditText;  
  24. import android.widget.ListView;  
  25. import android.widget.SimpleAdapter;  
  26. import android.widget.Toast;  
  27.   
  28. import com.jiahui.model.Person;  
  29.   
  30. public class ContentProviderDemoActivity extends Activity {  
  31.   
  32.     private Button btnadd, btnqueryall;  
  33.     private EditText edtname, edtage;  
  34.   
  35.     private ListView lvall;  
  36.   
  37.     private List<Person> persons;  
  38. private SimpleAdapter simpleAdapter;  
  39.     private Handler handler = new Handler() {  
  40.   
  41.         @Override  
  42.         public void handleMessage(Message msg) {  
  43.   
  44.             List<Map<String, Object>> data = (List<Map<String, Object>>) msg.obj;  
  45.   
  46.             System.out.println(data.size());  
  47.   
  48.               
  49.             simpleAdapter = new SimpleAdapter(  
  50.                     ContentProviderDemoActivity.this, data, R.layout.list_item,  
  51.                     new String[] { "id""name""age" }, new int[] {  
  52.                             R.id.tvId, R.id.tvname, R.id.tvage });  
  53.           
  54.             lvall.setAdapter(simpleAdapter);  
  55.               
  56.         }  
  57.   
  58.     };  
  59.   
  60.     public void onCreate(Bundle savedInstanceState) {  
  61.         super.onCreate(savedInstanceState);  
  62.         setContentView(R.layout.main);  
  63.   
  64.         persons = new ArrayList<Person>();  
  65.   
  66.         btnqueryall = (Button) this.findViewById(R.id.btnqueryall);  
  67.         btnadd = (Button) this.findViewById(R.id.btnadd);  
  68.         edtname = (EditText) this.findViewById(R.id.edtname);  
  69.         edtage = (EditText) this.findViewById(R.id.edtage);  
  70.         lvall = (ListView) this.findViewById(R.id.lvall);  
  71.   
  72.         btnadd.setOnClickListener(new View.OnClickListener() {  
  73.   
  74.             @Override  
  75.             public void onClick(View v) {  
  76.                 ContentResolver contentResolver = ContentProviderDemoActivity.this  
  77.                         .getContentResolver();  
  78.   
  79.                 Uri url = Uri  
  80.                         .parse("content://com.jiahui.provider.myprovider/person");  
  81.                 ContentValues values = new ContentValues();  
  82.                 values.put("name", edtname.getText().toString());  
  83.                 values.put("age", edtage.getText().toString());  
  84.                 Uri result = contentResolver.insert(url, values);  
  85.   
  86.                 System.out.println(result.toString());  
  87.                   
  88.                 if (ContentUris.parseId(result)>0) {  
  89.                     Toast.makeText(ContentProviderDemoActivity.this"添加成功", Toast.LENGTH_LONG).show();  
  90.                     //添加成功后再启动线程查询  
  91.                     MyThread thread = new MyThread(ContentProviderDemoActivity.this);  
  92.                     thread.start();  
  93.                 }  
  94.             }  
  95.         });  
  96.         //查询所有  
  97.         btnqueryall.setOnClickListener(new View.OnClickListener() {  
  98.   
  99.             @Override  
  100.             public void onClick(View v) {  
  101.               
  102.                 MyThread thread = new MyThread(ContentProviderDemoActivity.this);  
  103.                 thread.start();  
  104.             }  
  105.         });  
  106.   
  107.         lvall.setOnItemClickListener(new OnItemClickListener() {  
  108.   
  109.             @Override  
  110.             public void onItemClick(AdapterView<?> parent, View view,  
  111.                     int position, long id) {  
  112.                 // Toast.makeText(ContentProviderDemoActivity.this, position,  
  113.                 // Toast.LENGTH_LONG).show();  
  114.                 System.out.println("position:" + position);  
  115.   
  116.                 Person person = persons.get(position);  
  117.                 Bundle bundle = new Bundle();  
  118.                 bundle.putInt("id", person.getId());  
  119.   
  120.                 bundle.putString("name", person.getName());  
  121.   
  122.                 bundle.putInt("age", person.getAge());  
  123.   
  124.                 Intent intent = new Intent(ContentProviderDemoActivity.this,  
  125.                         ItemActivity.class);  
  126.                 intent.putExtra("item", bundle);  
  127.                 startActivityForResult(intent, 1);  
  128.   
  129.             }  
  130.         });  
  131.   
  132.     }  
  133.       
  134.     @Override  
  135.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  136.   
  137.         if (resultCode==2) {  
  138.             MyThread thread = new MyThread(ContentProviderDemoActivity.this);  
  139.             thread.start();  
  140.   
  141.         }  
  142.               
  143.     }  
  144.   
  145.   
  146.     class MyThread extends Thread {  
  147.   
  148.         Context context;  
  149.   
  150.         public MyThread(Context context) {  
  151.             //一定要清空。否则会 有问题,每执行一次都会把之前的全部的item加进去  
  152.             persons.clear();  
  153.             lvall.setAdapter(null);  
  154.               
  155.             this.context = context;  
  156.         }  
  157.   
  158.         @Override  
  159.         public void run() {  
  160.   
  161.             Uri url = Uri  
  162.                     .parse("content://com.jiahui.provider.myprovider/person");  
  163.   
  164.             Cursor cursor = context.getContentResolver().query(url,  
  165.                     new String[] { "_id""name""age" }, nullnull"_id");  
  166.   
  167.             while (cursor.moveToNext()) {  
  168.   
  169.                 // System.out.println("_id:"  
  170.                 // + cursor.getInt(cursor.getColumnIndex("_id")));  
  171.                 // System.out.println("name:"  
  172.                 // + cursor.getString(cursor.getColumnIndex("name")));  
  173.                 // System.out.println("age:"  
  174.                 // + cursor.getInt(cursor.getColumnIndex("age")));  
  175.                 Person person = new Person();  
  176.                 person.setId(cursor.getInt(cursor.getColumnIndex("_id")));  
  177.                 person.setName(cursor.getString(cursor.getColumnIndex("name")));  
  178.                 person.setAge(cursor.getInt(cursor.getColumnIndex("age")));  
  179.                 persons.add(person);  
  180.               
  181.             }  
  182.   
  183.             cursor.close();  
  184.               
  185.             List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();  
  186.   
  187.             Map<String, Object> map=null;  
  188.             for (int i = 0; i < persons.size(); i++) {  
  189.   
  190.                 map = new HashMap<String, Object>();  
  191.   
  192.                 map.put("id", persons.get(i).getId());  
  193.                 map.put("name", persons.get(i).getName());  
  194.   
  195.                 map.put("age", persons.get(i).getAge());  
  196.                 data.add(map);  
  197.   
  198.             }  
  199.             if (data.size()>=persons.size()) {  
  200.                   
  201.             }  
  202.             Message msg = handler.obtainMessage();  
  203.             msg.obj = data;  
  204.             handler.sendMessage(msg);  
  205.         }  
  206.   
  207.     }  
  208.   
  209. }  

 

ItemActivity代码:

[java]  view plain copy print ?
  1. package com.jiahui.provider;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.ContentResolver;  
  5. import android.content.ContentValues;  
  6. import android.content.Intent;  
  7. import android.net.Uri;  
  8. import android.os.Bundle;  
  9. import android.view.View;  
  10. import android.widget.Button;  
  11. import android.widget.EditText;  
  12. import android.widget.TextView;  
  13. import android.widget.Toast;  
  14.   
  15. public class ItemActivity extends Activity {  
  16.   
  17.     private EditText edt_item_name;  
  18.     private EditText edt_item_age;  
  19.     private EditText edt_item_id;  
  20.     private Button btndel, btnupdate;  
  21.   
  22.     private Intent intent;  
  23.   
  24.     @Override  
  25.     protected void onCreate(Bundle savedInstanceState) {  
  26.         // TODO Auto-generated method stub  
  27.         super.onCreate(savedInstanceState);  
  28.   
  29.         setContentView(R.layout.item);  
  30.   
  31.         edt_item_id = (EditText) this.findViewById(R.id.edt_item_id);  
  32.         edt_item_id.setEnabled(false);// 控制不可用  
  33.         edt_item_name = (EditText) this.findViewById(R.id.edt_item_name);  
  34.         edt_item_age = (EditText) this.findViewById(R.id.edt_item_age);  
  35.         // 得到传过来的数据  
  36.         btndel = (Button) this.findViewById(R.id.btndel);  
  37.         btnupdate = (Button) this.findViewById(R.id.btnupdate);  
  38.   
  39.         intent = getIntent();  
  40.   
  41.         Bundle bundle = intent.getBundleExtra("item");  
  42.   
  43.         int id = bundle.getInt("id");  
  44.         System.out.println("id----" + id);  
  45.         String name = bundle.getString("name");  
  46.         int age = bundle.getInt("age");  
  47.   
  48.         edt_item_id.setText(String.valueOf(id));  
  49.         edt_item_name.setText(name);  
  50.         edt_item_age.setText(String.valueOf(age));  
  51.   
  52.         btndel.setOnClickListener(new View.OnClickListener() {  
  53.   
  54.             @Override  
  55.             public void onClick(View v) {  
  56.                 ContentResolver contentResolver = ItemActivity.this  
  57.                         .getContentResolver();  
  58.                 // 构建Uri  
  59.                 String url = "content://com.jiahui.provider.myprovider/person/"  
  60.                         + edt_item_id.getText();  
  61.                 Uri uri = Uri.parse(url);  
  62.   
  63.                 int result = contentResolver.delete(uri, nullnull);  
  64.                 System.out.println("delete result:" + result);  
  65.   
  66.                 if (result >= 1) {  
  67.                     Toast.makeText(ItemActivity.this"删除成功", Toast.LENGTH_LONG)  
  68.                             .show();  
  69.                     ItemActivity.this.setResult(2);  
  70.                     ItemActivity.this.finish();  
  71.                 }  
  72.   
  73.             }  
  74.         });  
  75.   
  76.         btnupdate.setOnClickListener(new View.OnClickListener() {  
  77.   
  78.             @Override  
  79.             public void onClick(View v) {  
  80.   
  81.                 ContentResolver contentResolver = ItemActivity.this  
  82.                         .getContentResolver();  
  83.                 // 构建Uri  
  84.                 String url = "content://com.jiahui.provider.myprovider/person/"  
  85.                         + edt_item_id.getText();  
  86.                 Uri uri = Uri.parse(url);  
  87.                 ContentValues values = new ContentValues();  
  88.                 values.put("name", edt_item_name.getText().toString());  
  89.                 values.put("age",  
  90.                         Integer.parseInt(edt_item_age.getText().toString()));  
  91.                 int result = contentResolver.update(uri, values, nullnull);  
  92.                 System.out.println("update result:" + result);  
  93.                 if (result >= 1) {  
  94.                     Toast.makeText(ItemActivity.this"更新成功", Toast.LENGTH_LONG)  
  95.                             .show();  
  96.                     ItemActivity.this.setResult(2);  
  97.                     ItemActivity.this.finish();  
  98.                 }  
  99.   
  100.             }  
  101.         });  
  102.   
  103.     }  
  104.   
  105. }  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

一、Content Provider基本概念 

1ContentProvider为存储和获取数据提供了统一的接口。ContentProvide对数据进行封装,不用关心数据存储的细节。使用表的形式来组织数据。

Android学习十九:ContentProvider初步

2、使用ContentProvider可以在不同的应用程序之间共享数据。 

3Android为常见的一些数据提供了默认的ContentProvider包括音频、视频、图片和通讯录等   

ContentProvider所提供的函数

query(),insert(),update(),delete(),getType(),onCreate()等。

 二、URI(统一资源标识符)的使用方法

为系统的每一个资源给其一个名字,比方说通话记录。

1、每一个ContentProvider都拥有一个公共的URI这个URI用于表示这个ContentProvider所提供的数据。 

2Android所提供的ContentProvider都存放在android.provider包中。 将其分为ABCD 4个部分:

Android学习十九:ContentProvider初步

A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;"content://"

 BURI 的标识,它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的 类名。这个标识在 元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称;"content://hx.android.text.myprovider"

C:路径,不知道是不是路径,通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就ok了;"content://hx.android.text.myprovider/tablename"

D:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部;"content://hx.android.text.myprovider/tablename/#" #表示数据id

三、ContentProvider的实现过程

自己实现ContentProvider不常见因为可能不需要和别的应用程序交换数据。使用内置的ContentProvider比较多 

    1、定义一个CONTENT_URI常量,提供了访问ContentProvider的标识符。 

public static final Uri CONTENT_URI =Uri.parse("content://com.example.codelab.transportationprovider");

其中:content是协议

      Com.exmaple.codelab.transportationprovider是类名,包含完整的包名。

Uri.parse将一个字符串转换成Uri类型。

如果Provider包含子表,同样定义包含字表的CONTENT_URI

content://com.example.codelab.transportationprovider/train
content://com.example.codelab.transportationprovider/air/domestic
content://com.example.codelab.transportationprovider/air/international

然后定义列,确保里面包含一个_id的列。

    2、定义一个类继承ContentProvider 

public class FirstContentProvider extends ContentProvider

先介绍一下ContentProvider用到的UriMatcherUriMatcher的一个重要的函数是match(Uri uri)。这个函数可以匹配Uri,根据传入的不同Uri返回不同的自定义整形值,以表明Uri访问的不同资源的类型。

      例如

      public static final UriMatcher uriMatcher; 
      static { 
                     uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 
                     uriMatcher.addURI(Book.AUTHORITY, "item", Book.ITEM); 
                     uriMatcher.addURI(Book.AUTHORITY, "item/#", Book.ITEM_ID); 
              }

      这里UriMatcher类型的静态字段是用来匹配传入到ContentProvider中的Uri的类。其构造方法传入的匹配码是使用match()方法匹配根路径时返回的值,这个匹配码可以为一个大于零的数表示匹配根路径或传入-1,即常量UriMatcher.NO_MATCH表示不匹配根路径。 addURI()方法是用来增加其他URI匹配路径的,第一个参数传入标识ContentProviderAUTHORITY字符串。第二个参数传入需要匹配的路径,这里的#号为通配符,代表匹配任意数字,另外还可以用*来匹配任意文本。第三个参数必须传入一个大于零的匹配码,用于match()方法对相匹配的URI返回相对应的匹配码。 例如:sMatcher.addURI(com.test.provider.personprovider, “person, 1);如果match()方法匹配content://com.test.provider.personprovider/person路径,返回匹配码为1

    3、实现query,insert,update,delete,getTypeonCreate方法。 

    4、在AndroidManifest.xml当中进行声明。

<!-- android:name是完成ContentProvider类的全称
             android:authorities是和FirstProvidermetaData中的常量AUTHORITY的值一样否则会报错
         -->
        <provider android:name="com.bj.FirstContentProvider"
            android:authorities="com.bj.firstcontentprovider"
            />

 

 

四、具体代码

Activity19Activity.java

public class Activity19Activity extends Activity {

   

    private Button queryButton = null;

    private Button insertButton = null;

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.main);

       queryButton = (Button) this.findViewById(R.id.query);

       queryButton.setOnClickListener(newQueryListener());

       insertButton = (Button) this.findViewById(R.id.insert);

       insertButton.setOnClickListener(newInsertListener());

        System.out.println(getContentResolver().getType(FirstProvidermetaData.UserTableMetaData.CONTENT_URI));

    }

 

    class InsertListener implementsOnClickListener {

 

       @Override

       public void onClick(View v) {

           // TODOAuto-generated method stub

           ContentValues values = new ContentValues();

           values.put(FirstProvidermetaData.UserTableMetaData.USER_NAME,

                  "michal");

           Uri uri = getContentResolver()

                  .insert(

                         FirstProvidermetaData.UserTableMetaData.CONTENT_URI,

                         values);

           System.out.println("uri--->" + uri.toString());

       }

 

    }

 

    class QueryListener implementsOnClickListener {

       public void onClick(View v) {

           Cursor c = getContentResolver().query(

                  FirstProvidermetaData.UserTableMetaData.CONTENT_URI, null,

                  null, null, null);

           while (c.moveToNext()) {

              System.out.println(c.getString(c.getColumnIndex("username")));

 

           }

 

       }

    }

}

FirstContentProvider.java

public class FirstContentProvider extendsContentProvider {

    // 当别的程序来访问这个ContentProvider,是通过Uri来访问的,UriMatcher检查是否符合标准

    // uri起一个规则,返回数字

    public static final UriMatcher uriMatcher;

    // 下面定义两个规则

    public static final int INCOMING_USER_COLLECTION = 1;

    public static final int INCOMING_USER_SINGLE = 2;

    private DatabaseHelper dh;

    static {//下面的users前面不能加/

       uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

       uriMatcher.addURI(FirstProvidermetaData.AUTHORITY, "users",

              INCOMING_USER_COLLECTION);

       uriMatcher.addURI(FirstProvidermetaData.AUTHORITY, "users/#",

              INCOMING_USER_SINGLE);

    }

   

    //有点类似于sql里面表的别名,这个也是给列其别名,必须要用

    //列的别名还是原来的名,没必要修改

    public static HashMap<String,String> userProjectionMap;

    static{

       userProjectionMap = newHashMap<String,String>();

        userProjectionMap.put(UserTableMetaData._ID, UserTableMetaData._ID );

       userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME);

    }

 

    @Override

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

       // TODOAuto-generated method stub

       System.out.println("delete");

       return 0;

    }

 

    // 根据传入的URI,返回该URI所表示的数据类型

    // 也就是说,我们通过URI要访问的数据,返回什么类型

    @Override

    public String getType(Uri uri) {

       // TODOAuto-generated method stub

       System.out.println("getType");

       switch (uriMatcher.match(uri)) {

       case INCOMING_USER_COLLECTION:

           // UserTableMetaDataFirstProvidermetaData的内部类

           return UserTableMetaData.CONTENT_TYPE;

       case INCOMING_USER_SINGLE:

           return UserTableMetaData.CONTENT_TYPE_ITEM;

       default:

           throw new IllegalArgumentException("Unknown uri" + uri);

       }

    }

 

   

    @Override

    public Uri insert(Uri uri, ContentValues values) {

       // TODOAuto-generated method stub

       System.out.println("insert");

       SQLiteDatabase db = dh.getWritableDatabase();

       //返回表中自动增长的列的值,否则返回-1      

       long rowId = db.insert(UserTableMetaData.TABLE_NAME,null, values);   

       if(rowId>0){

           //rowId追加到后面

           //contentUris:用来处理Uri的工具类

           Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);

           //通知监听器,数据已经改变

           getContext().getContentResolver().notifyChange(insertedUserUri, null);

           return insertedUserUri;

       }

       throw new SQLException("Failed to insert row into "+uri);

    }

   

    //是一个回调方法,所以说在ContentProvider创建的时候执行

    //也就是创建这个DatabaseHelper对象

    @Override

    public boolean onCreate() {

       // TODOAuto-generated method stub

       //getContext得到当前正在运行着的context

       dh = new DatabaseHelper(getContext(),FirstProvidermetaData.DATABASE_NAME);

       System.out.println("on create");

        SQLiteDatabase db = dh.getReadableDatabase();

       return true;

    }

   

    //projection:查询的列有哪些

    //selection:where子句的内容,可以用?

    //selectionArgs:占位符对应的参数

    //sortOrder:排序

    @Override

    public Cursor query(Uri uri, String[] projection, String selection,

           String[] selectionArgs, String sortOrder) {

       // TODOAuto-generated method stub

       System.out.println("query");

       //创建一个查询的语句

       SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

       switch(uriMatcher.match(uri)){

       case INCOMING_USER_COLLECTION:

           //设置查询哪张表

           qb.setTables(UserTableMetaData.TABLE_NAME);

           qb.setProjectionMap(userProjectionMap);

           break;

       case INCOMING_USER_SINGLE:

           qb.setTables(UserTableMetaData.TABLE_NAME);

           qb.setProjectionMap(userProjectionMap);

           //添加where条件,getPathSegments:得到uripath部分content:XXX/user/1get(1)得到1

           qb.appendWhere(UserTableMetaData._ID+"="+uri.getPathSegments().get(1));

           break;

       }

      

       String orderBy;

       if(TextUtils.isEmpty(sortOrder)){

           orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;

       }

       else

       {

           orderBy = sortOrder;

       }

       SQLiteDatabase db = dh.getWritableDatabase();

       //下面的query使用qb这个对象

       Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);

       //也是通知下

       c.setNotificationUri(getContext().getContentResolver(), uri);

       System.out.println("query");

       return c;

    }

 

    @Override

    public int update(Uri uri, ContentValues values, String selection,

           String[] selectionArgs) {

       // TODOAuto-generated method stub

       return 0;

    }

 

}

FirstProvidermetaData.java

 

public class FirstProvidermetaData {

    public static final String AUTHORITY="com.bj.firstcontentprovider"; //继承了contentprovider的类的全名

    //数据库名称 

    public static final String DATABASE_NAME = "FirstProvider.db"; 

    //数据库的版本  

    public static final int DATABASE_VERSION = 1; 

    //表名  

    public static final String USERS_TABLE_NAME = "users"; 

   

    public static final class UserTableMetaData implements BaseColumns{

       //表名

       public static final String TABLE_NAME="users";

       //访问该ContentProviderURI

       public static final UriCONTENT_URI=Uri.parse("content://"+AUTHORITY+"/users");

      

       public static final StringCONTENT_TYPE="vnd.android.cursor.dir/vnd.firstprovider.user";

       public static final StringCONTENT_TYPE_ITEM="vnd.android.cursor.item/vnd.firstprovider.user";

       //列名,在users表中添加一个名为name的列    

       public static final String USER_NAME="name";

       //默认排序方式

       public static final String DEFAULT_SORT_ORDER="_id desc";

    }

 

}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值