赞
踩
特别吐槽一句,挺讨厌CSDN
的,把界面搞得跟特么广告网站似的。看的不烦吗?
链接:【Android】20.0 权限处理(二)——访问其他程序数据:读取联系人信息
主要写的是应用程序如何从另外一个应用程序中读取数据。
而这章的内容是相反的:如何给别的应用程序提供权限,以访问自己的数据。
本章内容可参考下面两篇相关文章:
链接:Android:关于ContentProvider的知识都在这里了!
链接:Android跨进程通信:图文详解 Binder机制 原理
这里涉及两个应用程序:
一个是之前已经写好的项目,DatabaseTest(所以该项目里面可能有与本篇内容无关的代码),主要作用是可以创建一个SQLite数据库,保存在该软件包名目录文件下。
另一个是新开的项目,ProviderTest,主要是用于操作DatabaseTest应用程序中的数据,可以进行增删查改。
两个项目的目录如下:
(Activity、Service、Broadcast Receiver、Content Provider)
关于什么是Android四大组件:
链接:android四大组件(组成)是什么,功能分别是
想要实现跨程序共享数据功能,官方推荐的方式就是这个——使用内容提供器。
一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProvider是以类似数据库中表的方式将数据暴露的。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URL来表示外界需要访问的“数据库”。
ContentProvider类有6个抽象方法,在使用子类继承它的时候,需要将这6个方法全部重写。比如新建一个MyProvider 继承自ContentProvider ,代码示范如下(本代码和上面所述的两个项目无关,但可以便于我们实际理解):
- package com.example.contactstest;
-
- import android.content.ContentProvider;
- import android.content.ContentValues;
- import android.database.Cursor;
- import android.net.Uri;
-
- public class MyProvider extends ContentProvider {
- @Override
- public boolean onCreate() {
- // TODO: Implement this to initialize your content provider on startup.
- //TODO: 实现此方法以在启动时初始化内容提供程序。
- return false;//返回true表示内容提供器创建成功
- }
-
- @Override
- public String getType(Uri uri) {
- // TODO: Implement this to handle requests for the MIME type of the data
- // TODO:实现此方法以处理对数据的mime类型的请求
- //根据传入的URI返回相应的MIME类型
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- // TODO: Implement this to handle requests to insert a new row.
- // TODO: 实现此方法以处理插入新行的请求。
- //uri:用于确定更新哪张表
- // values:数据都保存在这个参数内
- return null;//添加完成后,返回一个用于表示这条新纪录的URI
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- // TODO: Implement this to handle query requests from clients.
- //TODO:实现此方法以处理来自客户端的查询请求。
- //uri:用于确定查询哪张表
- //projection:用于确定查询哪列
- //selection和selectionArgs:用于约束确定查询哪些行
- //sortOrder:用于确定采用何种方式进行排序
- return null;
-
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- //TODO: Implement this to handle requests to delete one or more rows.
- //TODO:实现此方法以处理删除一行或多行的请求。
- // uri:用于确定删除哪张表中的数据
- //selection和selectionArgs:用于约束确定删除哪些行
-
- return 0;//被删除的行数将作为返回值返回
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- // TODO: Implement this to handle requests to update one or more rows.
- // TODO: 实现此方法以处理更新一行或多行的请求。
- // uri:用于确定更新哪张表中的数据
- // values:数据都保存在这个参数内
- // selection和selectionArgs:用于约束确定更新哪些行
- return 0;//受影响的行数将作为返回值返回
- }
- }
这里需要特别说明下的是getType( )
方法。
一个标准的内容URI的写法是这样的:content://com.example.app.provider/table1
知道什么是内容URI的程序员看一眼就知道,我们期望访问的是com.example.app这个应用的table1表中的数据,或者格式如下:content://com.example.app.provider/table1/1
表示我们期望访问的是com.example.app这个应用的table1表中id为1的数据
划线的重点来了:
1.内容URI主要有以上两种格式:
2.可以转成使用通配符来表示,通配符规则如下:*
:表示匹配任意长度的任意字符。#
:表示匹配任意长度的数字。
因此,内容URI分别可以改写成:content://com.example.app.provider/*
content://com.example.app.provider/table1/#
3.一个内容URI所对应的MIME字符串主要由3部分组成:
android.cursor.dir/
,如果内容URI以id结尾,则后接Android.cursor.item/
。vnd.<authority>.<path>
4.所以对于内容URIcontent://com.example.app.provider/table1
所对应的MIME类型格式为:vnd.android.cursor.dir/vnd.com.example.app.provider/table1
对于内容URIcontent://com.example.app.provider/table/1
所对应的MIME类型格式为:vnd.android.cursor.item/vnd.com.example.app.provider/table1/1
5.MIME数据类型
作用:指定某个扩展名的文件用某种应用程序来打开
如指定.html
文件采用text
应用程序打开、指定.pdf
文件采用flash
应用程序打开
(当然,这里其实用不到这个知识点,因为只有上面所述的两种写法)
OK,理论到位,接着往下走。
首先,activity_main.xml这个布局文件,不需要起作用(虽然里面有一个Button控件,所以这里就不贴代码了)
然后,MainActivity.java也不需要增加什么代码(虽然里面有个数据库创建的程序响应):
- package com.example.databasetest;
-
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Button;
-
- public class MainActivity extends AppCompatActivity {
-
- private MyDatabaseHelper dbHelper;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,1);
- Button createDatabase = (Button) findViewById(R.id.create_database);
- createDatabase.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dbHelper.getWritableDatabase();
- }
- });
- }
- }
-
- package com.example.databasetest;
-
- import android.content.Context;
- import android.database.sqlite.SQLiteDatabase;
- import android.database.sqlite.SQLiteOpenHelper;
- import android.widget.Toast;
-
- 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)";
-
- private Context mContext;
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_BOOK);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-
- }
-
- public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
- int version) {
- super(context, name, factory, version);
- mContext=context;
- }
- }
MyDatabaseHelper
类继承了SQLiteOpenHelper
类,这是一个抽象类,这个抽象类需要重写onCreate()
和onUpgrade()
方法,在这两个方法里面,我们可以实现创建、升级SQLite数据库。
com.example.databasetest
包名上右击,New→Other→Content Provider:会弹出如下窗口:
DatabaseProvider
,authority指定为
com.example.databasetest.provider
,
Exported
属性表示是否允许外部程序访问我们的内容提供器,
Enable
表示是否启用这个内容提供器,都勾上,点击finish创建:
接着修改DatabaseProvider中的代码:
- package com.example.databasetest;
-
- import android.content.ContentProvider;
- import android.content.ContentValues;
- import android.content.UriMatcher;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteDatabase;
- import android.net.Uri;
-
- public class DatabaseProvider 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.example.databasetest.provider";
- public static UriMatcher uriMatcher;
- private MyDatabaseHelper dbHelper;
-
- static {
- //uriMatcher中有个addURI()方法,这个方法接收三个参数
- // 分别把authority、path和一个自定义代码传进去。
- //这样可以调用uriMatcher的match()方法时可以将一个Uri对象传入
- //match()方法的返回值是某个能匹配这个Uri对象所对应的自定义代码
- //利用这个代码,我们就可以知道访问的是哪张表中的数据
- uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- //BOOk_DIR:表示访问book表中的所有数据
- uriMatcher.addURI(AUTHORITY, "book", BOOk_DIR);
- //BOOK_ITEM:表示访问book表中的单条数据
- uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
- //CATEGORY_DIR:表示访问category表中的所有数据
- uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
- //CATEGORY_ITEM:表示访问category表中的单条数据
- uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
- }
-
- public DatabaseProvider() {
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- //TODO: Implement this to handle requests to delete one or more rows.
- //TODO:实现此方法以处理删除一行或多行的请求。
- SQLiteDatabase db = dbHelper.getReadableDatabase();
- int deletedRows = 0;
- switch (uriMatcher.match(uri)) {
- case BOOk_DIR:
- deletedRows = db.delete("Book", selection, selectionArgs);
- break;
- case BOOK_ITEM:
- String bookId = uri.getPathSegments().get(1);
- deletedRows = db.delete("Book", "id = ?", new String[]{bookId});
- break;
- case CATEGORY_DIR:
- deletedRows = db.delete("Category", selection, selectionArgs);
- break;
- case CATEGORY_ITEM:
- String categoryId = uri.getPathSegments().get(1);
- deletedRows = db.delete("Category", "id = ?", new String[]{categoryId});
- break;
- default:
- break;
- }
- return deletedRows;
- }
-
-
- @Override
- public String getType(Uri uri) {
- // TODO: Implement this to handle requests for the MIME type of the data
- // TODO:实现此方法以处理对数据的mime类型的请求
- // at the given URI.
- switch (uriMatcher.match(uri)) {
- //这里可以看到对内容URI转为MIME类型的实际处理
- case BOOk_DIR:
- return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
- case BOOK_ITEM:
- return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
- case CATEGORY_DIR:
- return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
- case CATEGORY_ITEM:
- return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
- }
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- // TODO: Implement this to handle requests to insert a new row.
- // TODO: 实现此方法以处理插入新行的请求。
- SQLiteDatabase db = dbHelper.getReadableDatabase();
- Uri uriReturn = null;
-
- switch (uriMatcher.match(uri)) {
- case BOOk_DIR:
- case BOOK_ITEM:
- long newBookId = db.insert("Book", null, values);
- 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;
- }
- //因为insert()方法需要返回一个Uri对象,所以还需要把内容URI转成Uri对象
-
- return uriReturn;
- }
-
- @Override
- public boolean onCreate() {
- // TODO: Implement this to initialize your content provider on startup.
- //TODO: 实现此方法以在启动时初始化内容提供程序。
-
- dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- // TODO: Implement this to handle query requests from clients.
- //TODO:实现此方法以处理来自客户端的查询请求。
-
- SQLiteDatabase db = dbHelper.getReadableDatabase();
- Cursor cursor = null;
- switch (uriMatcher.match(uri)) {
- case BOOk_DIR:
- cursor = db.query("Book", projection, selection, selectionArgs, null, null,
- sortOrder);
- break;
- case BOOK_ITEM:
- //访问单条数据时用到这个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:
- cursor = db.query("Category", projection, selection, selectionArgs, null, null,
- sortOrder);
- break;
- case CATEGORY_ITEM:
- String categoryId = uri.getPathSegments().get(1);
- cursor = db.query("Category", projection, "id = ?", new String[]{categoryId},
- null, null, sortOrder);
- break;
- default:
- break;
- }
- return cursor;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- // TODO: Implement this to handle requests to update one or more rows.
- // TODO: 实现此方法以处理更新一行或多行的请求。
- SQLiteDatabase db = dbHelper.getReadableDatabase();
- int updateRows = 0;
- switch (uriMatcher.match(uri)) {
- case BOOk_DIR:
- updateRows = db.update("Book", values, selection, selectionArgs);
- break;
- case BOOK_ITEM:
- String bookId = uri.getPathSegments().get(1);
- updateRows = db.update("Book", values, "id = ?", new String[]{bookId});
- break;
- case CATEGORY_DIR:
- updateRows = db.update("Category", values, selection, selectionArgs);
- break;
- case CATEGORY_ITEM:
- String categoryId = uri.getPathSegments().get(1);
- updateRows = db.update("Category", values, "id = ?", new String[]{categoryId});
- break;
- default:
- break;
- }
- return updateRows;
- }
- }
代码说明都在代码块中。
AndroidManifest.xml
中注册,但是由于我们是使用Android studio
软件的快捷方式创建的,注册这一步已经自动完成了:- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.databasetest">
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <provider
- android:name=".DatabaseProvider"
- android:authorities="com.example.databasetest.provider"
- android:enabled="true"
- android:exported="true"></provider>
-
- <activity android:name=".MainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- </manifest>
<provider>
标签内就是,其中:
android:name
属性指定了DatabaseProvider的类名android:authorities
属性指定了DatabaseProvider的authorityandroid:enabled
属性和android:exported
属性上面已经说了,根据我们创建时打钩的状态自动生成的,表示DatabaseProvider允许被其他应用程序进行访问。一开始也说了,本篇内容只需要DatabaseTest这个应用程序在模拟器上装载即可,不要去点击“创建数据库”按钮,运行DatabaseTest项目后,可以把Android studio中DatabaseTest项目关闭了。
先看布局文件activity_main.xml
:
- <?xml version="1.0" encoding="utf-8"?>
- <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <Button
- android:id="@+id/add_data"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="添加数据到book"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.20"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.06" />
-
- <Button
- android:id="@+id/query_data"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="从book中查询数据"
- app:layout_constraintBottom_toBottomOf="@+id/add_data"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/add_data"
- app:layout_constraintTop_toTopOf="@+id/add_data" />
-
- <Button
- android:id="@+id/update_data"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="8dp"
- android:text="更新数据到book"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="@+id/add_data"
- app:layout_constraintStart_toStartOf="@+id/add_data"
- app:layout_constraintTop_toBottomOf="@+id/add_data"
- app:layout_constraintVertical_bias="0.06" />
-
- <Button
- android:id="@+id/delete_data"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="从book中删除数据"
- app:layout_constraintBottom_toBottomOf="@+id/update_data"
- app:layout_constraintEnd_toEndOf="@+id/query_data"
- app:layout_constraintStart_toStartOf="@+id/query_data"
- app:layout_constraintTop_toTopOf="@+id/update_data" />
-
- </android.support.constraint.ConstraintLayout>
效果很简单:
- package com.example.providertest;
-
- import android.content.ContentValues;
- import android.database.Cursor;
- import android.net.Uri;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Button;
- import android.widget.Toast;
-
- public class MainActivity extends AppCompatActivity {
-
- private String newId;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- Button addData = (Button) findViewById(R.id.add_data);
- addData.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //添加数据
- Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
- ContentValues values = new ContentValues();
- values.put("name", "王之国度");
- values.put("author", "张三");
- values.put("pages", 1040);
- values.put("price", 45.96);
- Uri newUri = getContentResolver().insert(uri, values);
- newId = newUri.getPathSegments().get(1);
-
- }
- });
-
- Button queryData = (Button) findViewById(R.id.query_data);
- queryData.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Uri uri = Uri.parse("content://com.example.databasetest.provider/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"));
- Toast.makeText(MainActivity.this,
- "name is" + name + ",\n author is" + author +
- ",\n pages is" + pages + ",\n price is" + price,
- Toast.LENGTH_SHORT).show();
- }
- cursor.close();
- }
- }
- });
-
- Button updateData = (Button) findViewById(R.id.update_data);
- updateData.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //更新数据
- Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
- ContentValues values = new ContentValues();
- values.put("name","创世之神");
- values.put("pages", 8250);
- values.put("price", 198.72);
- getContentResolver().update(uri, values, null, null);
- }
- });
-
- Button deleteData = (Button) findViewById(R.id.delete_data);
- deleteData.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
- //删除数据
- getContentResolver().delete(uri, null, null);
- }
- });
- }
- }
-
点击“添加数据到BOOK”按钮
将数据库导出,在Navicat Premium中查看:
关于如何导出数据库文件并查看请参考:
链接:【Android】18.0 SQLite数据库——LitePal的使用及SQLite数据库怎么查看
点击“从BOOK中查询数据”按钮:
点击“更新数据到BOOK”按钮,然后再次导出数据库,查看数据库:
点击“从BOOK中删除数据”按钮,再点击“从BOOK中查询数据”按钮,没有Toast提示响应。再次导出数据库,查看数据库:
就这么些意思。
备注:更全面的理解可以参考本篇一开始提供的参考链接,本篇有助于那些需要实现类似功能的朋友,可以直接照猫画虎,先让自己的构思实现起来。
再备注:说实话,自己挺反感写太多理论东西,当它们扎堆的时候,完全不知道怎么用好伐。
把东西放在实际项目中,傻瓜式地先串起来run成功后,写两遍知识点就学得差不多了,再回头看看理论,简直不要太好懂……
再再备注:除了一开始写的两三篇文章版本控制比较乱之外,自己写的代码可以说:
Android Studio3.3
版本Android
版本是7.0.0(API 24)
,但模拟器跑的Android
版本是Android9.0.0(API 28)
Windows10专业版64bit
再再再备注:真的很讨厌CSDN
……
END
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。