当前位置:   article > 正文

《第一行代码》笔记之 ContentProvider_android contentprovider创建数据失败

android contentprovider创建数据失败

本篇内容整理自郭霖的 《第一行代码》

访问其他应用中的数据——获取数据

ContentResolver 的基本用法

  • 访问内容提供器中的共享数据就必要借助 ContentResolver
  • 可以通过 Context 中的 getContentResolver() 方法获取实例
  • 提供了一些列的 CRUD 操作,类似 SQLiteDatabase。不同的是 CRUD 操作不提供表名,使用 Uri 参数代替
    • URI 权限:对于不同的应用程序做区分。一般采用程序包命名
    • URI 路径:同一程序中的不同的表做区分。
      标准的 URI 格式:content://com.example.app.provider(权限)/table1(路径)
      如果使用表名,系统将无法得知我们访问的是哪个应用程序中的表
  • 使用 Uri.parse() 方法,就可以将内容 URI 字符串解析为 Uri 对象
    Uri uri = Uri.parse("content://com.example.app.provider/table1");
  • 1

使用 Uri 对象来查询 table1 表中的数据

    Cursor cursor = getContentResolver().query(
    uri, //指定某个应用的某张表
    projection, //指定查询的列名
    selection, //指定 where 的约束条件
    selectionArgs, //为 where 中的占位符提供具体的值
    sortOrder // 排序方式
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

查询完毕,逐条读取数据

    if(cursor != null){
    while(cursor.moveToNext()){
        String column1 = cursor.getString(cursor.getColumnIndex("column1"));
        int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
    }
    cursor.close();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
  • 1
  • 2
  • 3
  • 4
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[]{ "text", "1" });
  • 1
  • 2
  • 3
getContentResolver().delete(uri,"column2 = ?", new String[]{ "1" });
  • 1

读取系统联系人

MainActivity.class

public class MainActivity extends Activity {

    ListView contactsView;

    ArrayAdapter<String> adapter;

    List<String> contacstList = new ArrayList<String>();

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

        contactsView = (ListView) findViewById(R.id.contacts_view);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contacstList);
        contactsView.setAdapter(adapter);
        readContacts();

    }

    private void readContacts() {
        Cursor cursor = null;
        try{
            //查询联系人
            cursor = getContentResolver().query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI, //获取手机通信录的 Uri
                    null,null,null,null
            );
            while (cursor.moveToNext()) {

                //获取联系人姓名
                String displayName = cursor.getString(cursor.getColumnIndex(
                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
                ));

                //获取联系人手机
                String number = cursor.getString(cursor.getColumnIndex(
                        ContactsContract.CommonDataKinds.Phone.NUMBER
                ));

                contacstList.add(displayName + "\n" + number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(cursor != null){
                cursor.close();
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

AndroidManifest.xml 中获取权限

 <uses-permission android:name="android.permission.READ_CONTACTS" />
  • 1

创建自己的内容提供器——提供数据

  • 之前是如何在自己的程序中访问其他应用的数据。思路:获取该应用程序的 URI,借助 ContentResolver 进行 CRUD 操作
  • 现在来实现将数据提供出去,给其他应用访问,并保证安全性。

创建内容提供器的步骤

创建个类继承 ContentProvider 类并重写它的六个抽象方法

public class MyProvider extends ContentProvider {

    //初始化内容提供器的时候调用。完成对数据库的创建和升级操作。返回 true 表示初始化成功,false 表示失败。
    //ContentResolver 尝试访问本程序中的数据时,内容提供器才会被初始化
    @Override
    public boolean onCreate() {
        return false;
    }

    //从内容提供器中查询数据
    //1、Uri 确定查询哪一张表
    //2、projection 确定查询哪些列
    //3、selection 和 selectionArgs 参数约束哪些行
    //4、sortOrder 对结果进行排序
    //查询结果存放在 Cursor 对象中返回
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }

    //向内容提供器中添加一条数据
    //1、URI 参数来确定添加哪一张表
    //2、待添加的数据保存在 values 中
    //返回一个用于表示这条新记录的 URI
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    //1、URI确定删除哪一张表
    //2、selection,selectionArgs 来约束删除哪些行
    //被删除的行将作为返回值
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    //1、URI确定更新哪一张表
    //2、新数据保存在 values 中
    //3、selection,selectionArgs 来约束更新哪些行
    //受影响的行将作为返回值
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    //根据传入内容的 URI 返回相应的 MIME 类型
    @Override
    public String getType(Uri uri) {
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 每一个方法都有一个 URI参数,此参数是调用ContentResolver 的增删改查方法时传递过来的。而我们需要对 Uri 参数进行解析,从中分析出调用方期望访问的表和数据

  • URI后面可以加 id,以路径结尾表示期望访问该表中的所有数据,以 id 结尾表示期望访问该表中拥有相应 id 的数据

  • 通配符方式匹配 URI

  • *****:表示匹配任意长度的任意字符

    • content://com.example.app.provider/*
  • #:表示匹配任意长度的数字

    • content://com.example.app.provider/#
public class MyProvider extends ContentProvider {

    public static final int TABLE1_DIR = 0;

    public static final int TABLE1_ITEM = 1;

    public static final int TABLE2_DIR = 2;

    public static final int TABLE2_ITEM = 3;

    private static UriMatcher uriMatcher;

    static{
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 将 uri 传入 uriMatcher
        uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
    }
    ......
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 在 uriMatcher 中逐个匹配 uri,选取调用方期望访问的那一张表
        switch (uriMatcher.match(uri)){
            case TABLE1_DIR:
                // 查询 table1 表中的所有数据
                break;
            case TABLE1_ITEM:
                // 查询 table1 表中的单条数据
                break;
            case TABLE2_DIR:
                // 查询 table2 表中的所有数据
                break;
            case TABLE2_ITEM:
                // 查询 table2 表中的单条数据
                break;
            default:
                break;
        }
        ......
    }
    ......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

上述代码只是用 query() 作为范例。insert()update()delete() 这个方法类似:1、它们都会携带 Uri 参数 2、利用 UriMatcher 的 match() 方法判断出调用方期望访问的是哪一张表,再对该表中的数据进行相应的操作就可以了。

  • 除此之外还有个 getType() 方法。用于获取 Uri 对象所对应的 MIME 类型。MIME 字符串由三部分构成:
  1. 必须以vnd 开头
  2. 如果内容以 URI 路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/。
  3. 最后接上 vnd.< authority >.< path >

列如
URIcontent://com.example.app.provider/table1
MIMEvnd.android.cursor.dir/vnd.com.example.app.provider.table1

  • 代码示例:
    public String getType(Uri uri) {
        switch(uriMatcher.match(uri)){
            case TABLE1_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
            case TABLE1_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
            case TABLE2_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
            case TABLE2_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
            default:
                break;
        }
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

综合实例

实现跨程序的数据共享

  • 程序对外开放内容

MyProvider.java

public class MyProvider extends ContentProvider {

    public static final int BOOK_DIR = 0;

    public static final int BOOK_ITEM = 1;

    public static final int CATEGORY_DIR = 2;

    public static final int CATEGORY_ITEM = 3;

    public static final String AUTHORITY = "com.czx.sunorig.myapplication";

    private MyDatabaseHelper dbHelper;

    private static UriMatcher uriMatcher;

    static{
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
        uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);
    }
    //初始化内容提供器的时候调用。完成对数据库的创建和升级操作。返回 true 表示初始化成功,false 表示失败。
    //ContentResolver 尝试访问本程序中的数据时,内容提供器才会被初始化
    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        return true;
    }

    //从内容提供器中查询数据
    //1、Uri 确定查询哪一张表
    //2、projection 确定查询哪些列
    //3、selection 和 selectionArgs 参数约束哪些行
    //4、sortOrder 对结果进行排序
    //查询结果存放在 Cursor 对象中返回
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;

        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                // 查询 book 表中的所有数据
                cursor = db.query("book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BOOK_ITEM:
                // 查询 book 表中的单条数据
                // getPathSegments() 方法将 URI 权限之后的部分以 “/” 符号进行分隔,并把分割结果放入一个字符串列表中,列表第0个位置是路径,第1个位置是 id
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("book", projection, "id = ?", new String[]{ bookId }, null, null, sortOrder);
                break;
            case CATEGORY_DIR:
                // 查询 category 表中的所有数据
                cursor = db.query("category", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case CATEGORY_ITEM:
                // 查询 category 表中的单条数据
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("category", projection, "id = ?", new String[]{ categoryId }, null, null, sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }

    //向内容提供器中添加一条数据
    //1、URI 参数来确定添加哪一张表
    //2、待添加的数据保存在 values 中
    //返回一个用于表示这条新记录的 URI
    @Override
    public Uri insert(Uri uri, ContentValues values) {

        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;

        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("book", null, values); //返回新添记录的行号,与主键id无关
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
                break;
            default:
                break;
        }
        return uriReturn;
    }

    //1、URI确定更新哪一张表
    //2、新数据保存在 values 中
    //3、selection,selectionArgs 来约束更新哪些行
    //受影响的行将作为返回值
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;

        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                updatedRows = db.update("book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book", values, "id = ?", new String[]{ bookId });
            case CATEGORY_DIR:
                updatedRows = db.update("category", values, selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book", values, "id = ?", new String[]{ categoryId });
            default:
                break;
        }
        return updatedRows;
    }

    //1、URI确定删除哪一张表
    //2、selection,selectionArgs 来约束删除哪些行
    //被删除的行将作为返回值
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {

        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deleteRows = 0;

        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                deleteRows = db.delete("book", selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deleteRows = db.delete("book", "id = ?", new String[]{ bookId });
                break;
            case CATEGORY_DIR:
                deleteRows = db.delete("category", selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deleteRows = db.delete("category", "id = ?", new String[]{ categoryId });
                break;
        }
        return deleteRows;
    }

    //根据传入内容的 URI 返回相应的 MIME 类型
    @Override
    public String getType(Uri uri) {
        switch(uriMatcher.match(uri)){
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd." + AUTHORITY + ".book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd." + AUTHORITY + ".category";
            default:
                break;
        }
        return null;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173

MyDatabaseHelper.java

public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table book("
            + "id integer primary key autoincrement,"
            + "author text,"
            + "price real,"
            + "pages integer,"
            + "name text)";

    public static final String CREATE_CATEGORY = "create table category("
            + "id integer primary key autoincrement,"
            + "book_id integer)";
    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

AndroidManifest.xml

        <provider
            android:authorities="com.czx.sunorig.myapplication"
            android:name="com.czx.sunorig.myapplication.MyProvider"
            android:exported="true">

        </provider>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 另一个程序访问内容:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button add_data;

    private Button query_data;

    private Button update_data;

    private Button delete_data;

    private String newId;

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

        add_data = (Button) findViewById(R.id.add_data);
        query_data = (Button) findViewById(R.id.query_data);
        update_data = (Button) findViewById(R.id.update_data);
        delete_data = (Button) findViewById(R.id.delete_data);

        add_data.setOnClickListener(this);
        query_data.setOnClickListener(this);
        update_data.setOnClickListener(this);
        delete_data.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.add_data:
                addData();
                break;
            case R.id.query_data:
                queryData();
                break;
            case R.id.update_data:
                updateData();
                break;
            case R.id.delete_data:
                deleteData();
                break;
            default:
                break;
        }
    }

    private void queryData() {
        Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book");
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        if(cursor != null){
            while(cursor.moveToNext()){
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String author = cursor.getString(cursor.getColumnIndex("author"));
                int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                double price = cursor.getDouble(cursor.getColumnIndex("price"));

                Log.d("MainActivity", "book name is " + name);
                Log.d("MainActivity", "book author is " + author);
                Log.d("MainActivity", "book pages is " + pages);
                Log.d("MainActivity", "book price is " + price);
            }
            cursor.close();
        }

    }

    private void addData() {
        Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book");

        ContentValues values = new ContentValues();
        values.put("name","A Clash of Kings");
        values.put("author", "George Martin");
        values.put("pages", 1040);
        values.put("price", 22.85);

        Uri newUri = getContentResolver().insert(uri, values);
        newId = newUri.getPathSegments().get(1);

    }

    private void updateData() {
        Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book");

        ContentValues values = new ContentValues();
        values.put("name", "A Storm of Swords");
        values.put("pages", 1216);
        values.put("price", 24.05);

        getContentResolver().update(uri, values, null, null);
    }

    private void deleteData() {
        Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book" + newId);
        getContentResolver().delete(uri, null, null);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号