【Android】 四大组件详解之广播接收器、内容提供器

目录

  • 前言
  • 广播机制
    • 简介
    • 系统广播
      • 动态注册实现监听网络变化
      • 静态注册实现开机自启动
    • 自定义广播
      • 发送标准广播
      • 发送有序广播
    • 本地广播
  • 内容提供器
    • 简介
    • 运行时权限
    • 访问其他程序中的数据
      • ContentResolver的基本用法
      • 读取系统联系人
    • 创建自己的内容提供器
      • 创建内容提供器的步骤
    • 跨程序数据共享实例
      • DatabaseProvider
      • ProviderTest
      • 注意注意

前言

本文用于介绍Android四大组件中的广播接收器以及内容提供器,希望对您有帮助。

广播机制

简介

首先我们要知道什么是广播:广播是Android操作系统中用于应用程序之间或应用程序内部进行通信的机制,它允许一个应用程序发送消息(广播事件),而其他应用程序可以接收并对这些消息做出响应

广播可以分为两种类型:标准广播有序广播

  • 标准广播:标准广播是一种完全异步执行的广播,在广播发出之后,所有广播接收器几乎同一时间接收到这条广播,它们之间没有任何先后顺序而言。这种广播的效率比较高,但同时也无法被截断
  • 有序广播:有序广播是一种同步执行的广播,在广播发出之后,同一时刻只有一个广播接收器能收到这条广播消息,只有当当前广播接收器将逻辑处理完之后广播才会继续传递,优先级高的广播先接到广播消息,并且前面的广播接收器可以截断正在传递的广播,后面的广播接收器就接不到消息了。

系统广播

系统广播是由Android操作系统自身发出的广播,用于通知应用程序有关设备状态和系统事件的变化。

注册广播有两种方式:动态注册->在代码中注册,静态注册->在AndroidManifest.xml中注册。
下面我们分别演示一下。

动态注册实现监听网络变化

动态注册只需新建一个类并让其继承自BroadcastReceiver并重写onReceive()方法即可。当有广播来时,onReceive()方法便会执行,执行这里面的具体逻辑。下面我们来演示动态监听网络状态发生改变。

public class MainActivity extends AppCompatActivity {//用于封装actionprivate IntentFilter intentFilter;private NetworkChangeReceiver networkChangeReceiver;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);intentFilter=new IntentFilter();//当网络状态发生变化时,系统发出一条值为android.net.conn.CONNECTIVITY_CHANGE的广播intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");//要监听什么广播,就在这添加相应的actionnetworkChangeReceiver=new NetworkChangeReceiver();registerReceiver(networkChangeReceiver,intentFilter);}@Overrideprotected void onDestroy(){super.onDestroy();//一定要记得取消注册unregisterReceiver(networkChangeReceiver);}class NetworkChangeReceiver extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context,"net work change",Toast.LENGTH_SHORT).show();}}
}

注意:动态注册的广播可以自由控制注册与注销,但有一个缺点是必须在程序启动之后才能接收到广播。要想实现让程序未启动的情况下就能接收到广播,那就必须使用静态注册的方法了。

静态注册实现开机自启动

创建一个广播接收器BootCompleteReceiver:

public class BootCompleteReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();}
}

只要我们是使用快捷方式来创建的广播接收器,系统会帮我们自动完成其注册,如创建出的静态广播接收器BootCompleteReceiver是会在AndroidManifest.xml文件中完成自动注册的,如下所示:

在这里插入图片描述

接着我们在注册好的广播的<intent-filter>标签里添加相应的action即可:

<intent-filter><action android:name="android.intent.action.BOOT_COMPLETED"/> 开机就会自动发送一个action为此值的广播
</intent-filter>

最后监听系统开机广播需要声明权限:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

这样我们在开机之后就会弹出一个Toast。

自定义广播

前面我们已经在知道广播主要分为标准广播和有序广播,接下来具体介绍一下这两种广播的具体用法。

发送标准广播

发送广播之前,我们要先创建一个广播接收器来接收此广播,新建MyBroadcastReceiver:

public class MyBroadReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_LONG).show();}
}

接着在AndroidManifest.xml中对广播进行注册(com.example.broadcasttest.MY_BROADCAST标识只接收值为这个的广播):

		<receiverandroid:name=".MyBroadReceiver"android:enabled="true"android:exported="true"><intent-filter><action android:name="com.example.broadcasttest.MY_BROADCAST"/></intent-filter></receiver>

最后点击按钮发送广播:

	Button button=(Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Log.d("MainActivity", String.valueOf(666));Intent intent=new Intent();intent.setAction("com.example.broadcasttest.MY_BROADCAST");intent.setPackage(getPackageName());//不可少,少了广播将无法被接收到sendBroadcast(intent);}});

启动项目点击发送即可发现屏幕上出现Toast。

发送有序广播

广播是一种可以跨进程的通信方式,因此在我们的应用程序内发出的广播,其他应用程序也可以接收到,只要其他应用程序的广播接收器的action与原本广播接收器的action一致。发送有序广播就允许我们随时截断这个广播

要想改发送标准广播为发送有序广播,只需将sendBroadcast(intent)改为sendOrderedBroadcast(intent,null)即可,另外我们需要去给广播接收器设置优先级,优先级越高,越先接收到广播,设置优先级如下所示:

		<receiverandroid:name=".MyBroadReceiver"android:enabled="true"android:exported="true"><intent-filter android:priority="100"><action android:name="com.example.broadcasttest.MY_BROADCAST"/></intent-filter></receiver>

最后,如果我们接收到广播后想截断广播,只需在广播接收器的onReceive()方法中加入abortBroadcast();即可让广播在此处截断。

本地广播

前面的我们发送和接收的广播全是属于系统全局广播,即发出的广播可以被其他应用程序接收到,并且我们也可以接收到其他应用程序的广播,这样容易引起安全性问题。

为此Android提供了一套本地广播机制,使用这个机制发出的广播只能在应用程序内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播。本地广播也是我们使用到的更多的广播。

下面提供一个使用本地广播的实例:

public class MainActivity extends AppCompatActivity {private IntentFilter intentFilter;private LocalReceiver localReceiver;private LocalBroadcastManager localBroadcastManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获取实例localBroadcastManager=LocalBroadcastManager.getInstance(this);Button button=(Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Intent intent=new Intent("com.example.broadcasttest.LOCAL_BROADCAST");//发送本地广播localBroadcastManager.sendBroadcast(intent);}});intentFilter=new IntentFilter();intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");localReceiver=new LocalReceiver();//注册本地广播监听器,监听值为action的广播localBroadcastManager.registerReceiver(localReceiver,intentFilter);}@Overrideprotected void onDestroy(){super.onDestroy();localBroadcastManager.unregisterReceiver(localReceiver);}class LocalReceiver extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context,"receive local broadcast",Toast.LENGTH_SHORT).show();}}}

要实现本地广播,我们需要用一个LocalBroadcastManager来对广播进行管理,用IntentFilter来封装我们的action。

当然如果这样你看的不是很懂的话,我们换一种方式。

  • 新建Local2Receiver:
public class Local2Receiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context,"receive local broadcast666",Toast.LENGTH_SHORT).show();}
}
  • 在MainActivity中:
public class MainActivity extends AppCompatActivity {private IntentFilter intentFilter;private LocalBroadcastManager localBroadcastManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获取实例localBroadcastManager=LocalBroadcastManager.getInstance(this);Button button2=(Button) findViewById(R.id.button2);button2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Intent intent=new Intent("111");localBroadcastManager.sendBroadcast(intent);}});intentFilter=new IntentFilter();intentFilter.addAction("111");Local2Receiver local2Receiver=new Local2Receiver();localBroadcastManager.registerReceiver(local2Receiver,intentFilter);}
}

可以看到,在MainActivity中,我们首先获取了LocalBroadcastManager实例用来对广播进行管理,接着当我们点击按钮发送一条action为111的广播时,接下来我们注册本地广播监听,两个action一致,这样我们就可以让Local2Receiver广播接收器里的onReceive()方法得到执行了。

内容提供器

本章介绍的是Android四大组件之一内容提供器。在最后面提供了一个跨程序数据共享实例来参考学习。

简介

内容提供器的主要功能是用于在不同的应用程序之间实现数据的共享。它允许一个程序访问另一个程序中的数据,同时保持被访问数据的安全性。除此之外,内容提供器还能选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不被泄露。

在详细讲解内容提供器的内容之前,我们需要先学习一下Android运行时权限。

运行时权限

我们常用的就两类权限,一类是普通权限,另一类是危险权限。普通权限不会直接威胁到用户的安全和隐私,只需去AndroidManifest.xml文件中注册一下即可;危险权限是可能会触及用户的隐私或对设备的安全性造成影响的权限,我们不仅需要去AndroidManifest.xml文件中注册一下,还必须要去手动申请这部分权限,否则程序无法使用相应的功能。

申请危险权限的实例如下所示:

  • AndroidManifest.xml
<!--    注册要申请的权限-->
<uses-permission android:name="android.permission.CAMERA"/>
  • MainActivity
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button makeCall=(Button) findViewById(R.id.make_call);makeCall.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//检查需要的权限是否被授权if(ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){//未授权,请求权限ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},1);}else {call();}}});}//授权成功做的事private void call(){}@Overridepublic void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode){case 1:if(grantResults.length>0&& grantResults[0]==PackageManager.PERMISSION_GRANTED){//授权成功,执行操作call();}else {//拒绝授权Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();}break;default:}}
}

借助ContextCompat.checkSelfPermission()方法判断是否授权,在这里面我们要声明具体申请的权限名,用户未授权的话调用ActivityCompat.requestPermissions()方法申请权限。

访问其他程序中的数据

内容提供器的用法一般有两种:

  • 使用现有的内容提供器(系统提供的)来读取和操作相应程序中的数据
  • 创建自己的内容提供器给我们程序的数据提供外部访问接口

这里我们先来介绍第一种用法——使用现有的内容提供器。

如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那其他的应用程序都可以对这部分数据进行数据访问。如电话簿就提供了类似的访问接口。我们先来学习一下ContentResolver的基本用法再去访问系统联系人的数据。

ContentResolver的基本用法

想访问内容提供器中的共享数据,必须使用ContentResolver类,我们可以通过Context中的getContentResolver()方法获取到这个类的实例。ContentResolver提供了一系列方法对数据进行CRUD操作,其中insert()用于添加数据、update()用于更新数据、delete()用于删除数据、query()用于查询数据。

说到这可能你会想到这个不是和SQLiteDatabase相似吗,但其实还是有些地方不同的。SQLiteDatabase是根据数据库名再根据表名来进行数据库操作。

ContentResolver不涉及到数据库名、表名,而是使用一个Uri参数代替,这个Uri参数被称为内容URI,内容URI分为两部分——authority和path,authority是用于对不同的应用程序进行区分的,如某程序包名是com.example.app,那么它的authority就可以命名为com.example.app.provider;path则是用于对应用程序中的表进行区分,并通常将其添加到authority后面,如该应用程序数据库中存在一张表table1,那么其path就可以命名为/table1,将authority与path组合,内容URI就变成了com.example.app.provider/table1,为了更容易地辨识出该字符串是内容URI,我们还需要在字符串头部加上协议声明content://,所以一个完整的内容URI就可以写成content://com.example.app.provider/table1

在这里插入图片描述

得到内容URI字符串后,我们需要将其解析为Uri对象,只需调用Uri的parse()方法即可:

Uri uri=Uri.parse("content://com.example.app.provider/table1");

接下来我们就可以使用这个Uri对象来查询table1中的数据了:

Cursor cursor=getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder
);

下面对这些参数进行一些详解:

参数对应的SQL部分描述
urifrom tabel_name指定查询某应用程序下的某张表
projectionselect column1,column2指定查询的列名
selectionwhere column=value指定where的约束条件
selectionArgs-为where中的占位符提供具体的值
sortOrderorder by column1,column2指定查询结果的排列方式

查询结束后给我们返回的仍是一个Cursor对象,接下来就可以像查询数据库操作一样的,将数据依次从Cursor对象中逐个读取出来:

if(cursor!=null){while(cursor.moveToNext()){String column1=cursor.getString(cursor.getColumnIndex("column1"));int column2=cursor.getInt(cursor.getColumnIndex("column2"));}cursor.colse();
}
  • 添加操作
ContentValues values=new ContentValues();
values.put("column1","text");
values.put("column2",1);
getContentResolver().insert(uri,values);
  • 更新数据
ContentValues values=new ContentValues();
getContentResolver().update(uri,values,"column1=? and column2=?",new String[]{"text","1"});
  • 删除数据
//删除column2=1的那条数据
getContentResolver().delete(uri,"column2=?",new String[]{"1"});

读取系统联系人

我们首先先去联系人里添加几个数据。

在这里插入图片描述

  • 使用一个ListView用来展示查询的联系人数据
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"><ListViewandroid:id="@+id/contacts_view"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>
  • 在AndroidManifest.xml里声明添加联系人权限
<uses-permission android:name="android.permission.READ_CONTACTS"/>
  • 在MainActivity中
public class MainActivity extends AppCompatActivity {ArrayAdapter<String> adapter;List<String> contactsList=new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ListView contactsView=(ListView) findViewById(R.id.contacts_view);adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,contactsList);contactsView.setAdapter(adapter);//检查需要的权限是否被授权if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){//未授权,请求权限ActivityCompat.requestPermissions(this,new String[]{android.Manifest.permission.READ_CONTACTS},1);}else {readContacts();}}//读取系统联系人private void readContacts(){Cursor cursor=null;try {//查询联系人数据cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);if(cursor!=null){while(cursor.moveToNext()){//获取联系人姓名@SuppressLint("Range") String displayName=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));//获取联系人手机号@SuppressLint("Range") String number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));contactsList.add(displayName+"\n"+number);}adapter.notifyDataSetChanged();}}catch (Exception e){e.printStackTrace();}finally {if(cursor!=null){cursor.close();}}}@Overridepublic void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode){case 1:if(grantResults.length>0&& grantResults[0]==PackageManager.PERMISSION_GRANTED){//授权成功,执行操作readContacts();}else {//拒绝授权Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();}break;default:}}
}

我们主要来看readContacts()方法,其他都是申请权限相关的。在readContacts()方法中,我们的query()查询方法不是需要传入一个Uri参数吗,这里其实是ContactsContract.CommonDataKinds.Phone这个类替我们做好了封装,它的CONTENT_URI常量就是我们使用Uri.parse()方法解析出来的Uri参数,这就表示我们要使用内容提供器读取系统联系人的数据,另外联系人姓名和手机号也做好了封装,分别对应DISPLAY_NAME、NUMBER。最终我们就可以将从系统联系人中读到的数据展示在ListView上了。

创建自己的内容提供器

创建内容提供器的步骤

要创建自己的内容提供器(在这个内容提供器中,我们要完成执行数据库逻辑的操作,在别的应用程序中就可以通过这个内容提供器提供的方法来执行逻辑),只需创建出一个类继承自ContentProvider即可,新建MyProvider,其中我们要重写六个方法:

  • onCreate():在这完成数据库的创建和升级等操作,返回true表示内容提供器初始化成功,返回false表示失败
  • query():从内容提供器查询数据
  • insert():向内容提供器插入一条数据
  • update():更新内容提供器中已有的数据
  • delete():从内容提供器中删除数据
  • getType():根据传入的内容URI返回相应的MIME类型

上面的方法中,除了onCreate()方法,每个方法都需要传入一个Uri,这里我们对Uri进行一下解析,分析一下调用方期望访问的表和数据。标准的Uri写法如下:

content://com.example.app.provider/table1

这就表示我们期望访问的是com.example.app这个应用的table1表中的数据,我们还可以在后面加上一个id:

content://com.example.app.provider/table1/1

表示我们要访问的是com.example.app这个应用的table1表中的id为1的数据。内容URI的格式主要就以上两种,以路径结尾表示访问表中的所有数据,以id结尾表示访问表中相应id的数据。

另外我,还可以使用通配符来匹配上面两种格式的URI:

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

所以一个能匹配任意表的内容URI可以写成:

content://com.example.app.provider/*

一个能够匹配table1表中任意一行数据的内容URI可以写成

content://com.example.app.provider/table1/#

接着我们可以借助UriMatcher这个类可以轻松实现匹配内容URI,UriMatcher提供了一个addURI()方法,这个方法接收三个参数,可以把authority、path和一个自定义代码传进去,这样当我们调用UriMatcher的match()方法时就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了(传过来的Uri匹配的是哪个,就去哪个case执行对应的逻辑),MyProivder中的代码如下所示:

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;//期望匹配的URIstatic{uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);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);}@Overridepublic boolean onCreate() {//完成对数据库的创建和升级等操作,返回true表示内容提供器初始化成功,返回false表示失败return false;}@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {//从内容提供器查询数据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:}return null;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {return null;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {//向内容提供器插入一条数据return null;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {//从内容提供器删除一条数据return 0;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {//从内容提供器更新一条数据return 0;}
}

当query()方法被调用时,就会通过UriMatcher的match()方法对传入的Uri对象进行匹配,如果发现UriMatcher中某个内容URI格式成功匹配了该Uri对象,就会返回相应的自定义代码,然后我们就可以判断出调用方期望访问的到底是什么数据了,接着只需要去对应的case下执行具体的逻辑。

最后就是一个getType()方法,它是所有内容提供器都必须提供的方法,用于获取Uri对象的MIME类型,一个内容URI的MIME字符串主要有三部分组成,Android对这三部分做了如下格式规定:

  • 必须以vnd开头
  • 如果内容URI以路径结尾,则vnd后接android.cursor.dir/;如果内容以id结尾,则后接android.cursor.item/
  • 最后接上vnd.<authority>.<path>

所以对于content://com.example.app.provider/table1这个内容URI,它对于的MIME类型可以写成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

对于content://com.example.app.provider/table1/1这个内容URI,它对应的MIME类型可以写成:

vnd.android.cursor.item/vnd.com.example.app.provider.table1

接着完善getType()方法:

	@Overridepublic String getType(@NonNull Uri uri) {//根据传入的内容URI来返回相应的MIME类型switch (uriMatcher.match(uri)){case TABLE1_DIR:return "vnd.android.cursor.dir/com.example.app.provider.table1";case TABLE1_ITEM:return "vnd.android.cursor.item/com.example.app.provider.table1";case TABLE2_DIR:return "vnd.android.cursor.dir/com.example.app.provider.table2";case TABLE2_ITEM:return "vnd.android.cursor.item/com.example.app.provider.table2";default:break;}return null;}

到这一个完整的内容提供器就完成了,现在任何一个应用程序都可以使用ContentResolver来访问我们程序中的数据了。另外在我们创建内容提供器的过程中,就完成了保证隐私数据不被泄露的操作,我们在向UriMatcher中添加URI的时候,只要不去添加含隐私数据的URI即可,隐私数据无法被访问到,安全问题不久解决了。UriMatcher就是用来完成确定内容URI是否匹配的

小结:ContentResolver想进行增删改查操作,就必须传个Uri对象到方法中。而在我们自定义的内容提供器中可以使用UriMatcher来先定义好几个Uri对象,也就是说当在别处调用了增删改查方法时,传进去的Uri对象会与自定义内容提供器中通过UriMatcher定义好的Uri进行对比,只有能够匹配时我们才去在内容提供器中进行增删改查操作。

跨程序数据共享实例

大致过程:首先我们需要一个应用程序来充当内容提供器的数据供应方,这里我新建了一个项目DatabaseProvider,并在此应用程序的数据库中创建出一个Book表(数据库名为BookStore.db),同时在这个项目中实现了一个内容提供器用于共享其数据,具体在下面演示。接着我新建了一个项目ProviderTest,用来去访问DatabaseProvider这个程序,并对其Book表进行增删改查操作。

DatabaseProvider

  • 首先创建一个MyDatabaseHelper用于创建数据库和Book表
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,"+"image blob,"+"name text)";private Context mContext;public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);mContext=context;}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_BOOK);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}

注意:在这里我们不能使用Toast,跨程序访问不允许我们使用Toast。

  • 最重要的一步,实现一个内容提供器DatabaseProvider(这个项目的名字也叫DatabaseProvider,记得区分开),通过该内容提供器就可以给外部提供一个访问接口,对本应用程序的数据库进行操作
public class DatabaseProvider extends ContentProvider {public static final int BOOK_DIR=0;public static final int BOOK_ITEM=1;public static final String AUTHORITY="com.example.databaseprovider.provider";private static UriMatcher uriMatcher;private MyDatabaseHelper dbHelper;//期望匹配的URIstatic {uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);}@Overridepublic boolean onCreate() {dbHelper=new MyDatabaseHelper(getContext(),"BookStore.db",null,1);return true;}@Overridepublic 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://匹配的是查询整个表,条件不做设置cursor= db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);break;case BOOK_ITEM://匹配的是根据id查询表,设置条件String bookId=uri.getPathSegments().get(1);cursor=db.query("Book",projection,"id=?",new String[]{bookId},null,null,sortOrder);break;default:break;}return cursor;}@Overridepublic 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);uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);break;default:break;}return uriReturn;}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {//更新数据SQLiteDatabase db=dbHelper.getWritableDatabase();int updateRow=0;switch (uriMatcher.match(uri)){case BOOK_DIR:updateRow= db.update("Book",values,selection,selectionArgs);break;case BOOK_ITEM:String bookId=uri.getPathSegments().get(1);updateRow=db.update("Book",values,"id=?",new String[]{bookId});break;default:break;}return updateRow;}@Overridepublic 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;default:break;}return deleteRows;}@Overridepublic String getType(Uri uri) {switch (uriMatcher.match(uri)){case BOOK_DIR:return "vnd.android.cursor.dir/vnd.com.example.databaseprovider.provider.book";case BOOK_ITEM:return "vnd.android.cursor.item/vnd.com.example.databaseprovider.provider.book";}return null;}
}

在该内容提供器中,我们定义了两个常量分别用于表示访问Book表中的所有数据访问Book表中的单条数据。接着在静态代码块中对UriMatcher进行了初始化操作并添加了两个期望匹配的URI格式。在onCreate()方法中创建了一个MyDatabaseHelper实例,用于下面的增删改查操作。接下来我们对这增删改查方法做一下基本讲解:

  • query():我们调用了uriMatcher.match()方法用于对我们想进行访问的数据进行匹配,通过这个方法我们可以得知期望访问的是哪张表中的哪些数据,接着根据不同需求完成数据的查询操作,最后将我们查询到的Cursor对象返回,在数据调用方遍历这个Cursor对象即可得到我们访问数据库得到的数据。
  • insert():在插入操作我们将数据插入进数据库的同时返回一个id,并将这个根据id组成一个URI返回给数据调用方,我们可以根据这个id执行增删改操作。
  • update()、delete():这两个方法没什么好说的,执行完操作后返回一个整型数据表示受影响的数据的行数。

至此,我们就可以将本项目启动一下再关闭了。接下来创建一个ProviderTest项目用于对DatabaseProvider进行访问及增删改查操作。

ProviderTest

  • 首先我们在布局中定义四个按钮进行增删改查操作
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"android:orientation="vertical"tools:context=".MainActivity"><Buttonandroid:id="@+id/add_data"android:text="Add To Book"android:layout_width="match_parent"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/query_data"android:text="Query From Book"android:layout_width="match_parent"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/update_data"android:text="Update Book"android:layout_width="match_parent"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/delete_data"android:text="Delete From Book"android:layout_width="match_parent"android:layout_height="wrap_content"/>
</LinearLayout>
  • 接着我们就可以在MainActivity中对数据进行增删改查了
public class MainActivity extends AppCompatActivity {private String newId;@Overrideprotected 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() {@Overridepublic void onClick(View view) {//添加数据,表示我们想查询的是com.example.databaseprovider这个应用程序的Book表中的数据Uri uri=Uri.parse("content://com.example.databaseprovider.provider/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);}});Button queryData=(Button) findViewById(R.id.query_data);queryData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//查询数据Uri uri=Uri.parse("content://com.example.databaseprovider.provider/book");Cursor cursor=getContentResolver().query(uri,null,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 are "+pages);Log.d("MainActivity","book price is "+price);}cursor.close();}}});Button updateData=(Button) findViewById(R.id.update_data);updateData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//更新数据Uri uri=Uri.parse("content://com.example.databaseprovider.provider/book/"+newId);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);}});Button deleteData=(Button) findViewById(R.id.delete_data);deleteData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//删除数据Uri uri=Uri.parse("content://com.example.databaseprovider.provider/book/"+newId);getContentResolver().delete(uri,null,null);}});}
}
  • 在插入数据中,我们先调用Uri.parse()方法将内容URI解析成Uri对象,这就表示我们想对com.example.databaseprovider这个应用程序的Book表进行访问,然后封装好一个ContentValues对象,接着调用ContentResolver的insert方法将数据插入即可,这个方法会返回一个id,在下面我们可以根据这id执行更新和删除数据的逻辑。
  • 在查询数据时,当我们调用query()方法时,上面我们讲到了这个方法会返回一个Cursor对象,这个对象里面有我们在DatabaseProvider里查询到的数据,这样我们就可以在ProviderTest这个程序中将数据取出来。

至此,当我们启动ProviderTest这个项目并点击添加数据的按钮时,我们可以打开DatabaseProvider的数据库进行查询,如下所示:

在这里插入图片描述

我们再点击查询数据的按钮时,控制台输出如下:

在这里插入图片描述

可以看到在ProviderTest中查询出来的数据和DatabaseProvider数据库中的数据一样,这样我们就实现了跨程序数据共享的功能。

注意注意

当你点击添加数据的按钮时,你可能会遇到UnKnown URL content://com.example…报错,这是因为在Android11之后,Android更改了程序间访问数据的方式,我们需要在AndroidManifest.xml文件中加入<queries>标签,如下所示:

  • 在DatabaseProvider中:

在这里插入图片描述

  • 在ProviderTest中

在这里插入图片描述
分享到此结束,希望对您有帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/3752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vivado 创建和运行链路清扫

创建和运行链路清扫 要分析给定链路的裕度 &#xff0c; 利用不同 MGT 设置来多次运行链路扫描是很有效的。这样有助于判定最佳设置。 Vivado Serial I/O Analyzer 功能支持您定义、运行、保存和重新调用链路清扫 &#xff0c; 链路清扫是由多次链路扫描集合而成的。 每条…

HTML中的文档声明

前言 什么是<!DOCTYPE>&#xff1f;是否需要在 HTML5 中使用&#xff1f;什么是严格模式与混杂模式&#xff1f; 文档声明概念 HTML 文档通常以文档声明开始&#xff0c;该声明的作用是帮助浏览器确定其尝试解析和显示的 HTML 文档类型。 <!DOCTYPE html>文档声…

Allure精通指南(05)定制化报告内容(环境信息、图标、缺陷类别)

文章目录 Allure 自定义测试环境信息Allure 自定义缺陷类别信息Allure 自定义图标步骤一步骤二步骤三 Allure 自定义测试环境信息 步骤 1&#xff1a;创建 environment.properties 文件 在项目根目录或任何其他不会被--clean-alluredir参数影响的目录下创建 environment.proper…

【链表】Leetcode K个一组翻转链表

题目讲解 25. K 个一组翻转链表 算法讲解 虽然这道题是一道困难题&#xff0c;但是从代码层面很简单&#xff0c;只是一道简单的模拟&#xff1a;我们要先求出总共需要翻转的链表有多少组&#xff08;链表的长度 / k&#xff09;&#xff0c;接下来就是翻转k的链表最链接的问…

Tomcat安装步骤及详细配置教程(2022最新版)

网上的tomcat安装及配置教程一大堆&#xff0c;但是好多都过时了&#xff0c;根本不适用现在的版本&#xff0c;今天凯歌整理一篇Tomcat安装步骤及详细配置教程&#xff0c;2022年最新版~ Tomcat安装及配置教程主要分为四步&#xff1a; 步骤一&#xff1a;首先确认自己是否已…

【C++进阶之路】C++11(下) —— 线程库

序言 本篇文章主要是填之前C11留下的坑以及了解与熟悉线程库&#xff0c;有读者感兴趣之前的内容的话可见「C进阶之路」专栏中标题为「C11」的内容&#xff0c;废话不多说&#xff0c;先来概括一下本文的内容&#xff0c;首先我们会从历史的角度分别谈及Linux以及Windows下的线…

JavaEE 初阶篇-深入了解 I/O 高级流(缓冲流、交换流、数据流和序列化流)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 缓冲流概述 1.1 缓冲流的工作原理 1.2 使用缓冲流的步骤 1.3 字节缓冲流于字符缓冲流的区别 1.4 字节缓冲流的实例 1.5 字符缓冲流的实例 2.0 转换流概述 2.1 字符…

13-Makefile_04

使用函数 在更复杂的工程中&#xff0c;头文件、源文件可能会放在二级目录&#xff0c;为了实现这种操作通常需要使用Makefile的函数。 函数格式及示例 在Makefile中调用函数的方法跟变量的使用类似&#xff0c;以“$()”或“${}”符号包含函数名和参数&#xff0c;具体语法…

区块链技术与应用学习笔记(8-9节)——北大肖臻课程

目录 8.挖矿 对于全节点和轻节点思考问题&#xff1f; ①全节点在比特币的主要作用&#xff1f; ②挖矿时当监听到别人已经挖出区块并且延申了最长合法链此时应该立刻放弃当前区块在 本地重新组装一个指向最后这个新合法区块的候选区块&#xff0c;重新开始挖矿。节点这么做…

【C++】STL-vector的使用

目录 1、什么是vector&#xff1f; 2、vector的使用 2.1 vector的定义 ​编辑 2.2 遍历修改数据 2.3 迭代器 2.4 vector空间增长问题 2.5 vector的增删查改 3、迭代器失效 3.1 会引起其底层空间改变的操作&#xff0c;都有可能是迭代器失效 3.2 指定位置元素的删除操…

stable diffusion Temporal-kit和EbSynth视频转动画学习笔记

1、打开stable diffsuion webui 点击Temporal-kit 页签&#xff0c;再点击预处理pre-processing,上传视频 在工作目录下得到拆分的关键帧,在input目录里 打开图生图&#xff0c;输入正反描述词&#xff0c;其他配置如下 批量生成图片&#xff0c;找到最满意的那一张&#xff0…

python-opencv实现最近邻插值和双线性插值对图片上采样

使用背景 当我们需要把图像进行放大或者缩小的时候&#xff0c;第一反应是使用resize()实现。很多情况下&#xff0c;我们会调用最近邻插值和双线性插值去放大图片&#xff0c;当然要说没有分辨率的损失那是不可能的&#xff0c;只能说在放大图片的过程中尽可能增加了图片的分…

stm32开发之netxduo组件之mqtt客户端的使用记录

前言 1使用mqtt协议的简单示例记录 代码 MQTT服务端(C# 编写,使用MQTTnet提供的示例代码) 主程序 namespace ConsoleApp1;public class Program {public static async Task Main(string[] args){await Run_Server_With_Logging();}}public static async Task Run_Server_Wi…

js如何点击生成4位随机数

效果图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Generat…

软件开发最近很吃香,嵌入式建议转行吗?

在当今时代&#xff0c;软件开发确实备受瞩目&#xff0c;也相当吃香。那么&#xff0c;对于嵌入式领域&#xff0c;我们是否应该考虑转行呢&#xff1f; 事实上&#xff0c;嵌入式工程师的薪资水平相较于互联网行业&#xff0c;在某些情况下可能会略低一些&#xff0c;尤其是…

对于AIGC(人工智能)我们应该如何看待

文章目录 前言一、AIGC技术的现状与特点二、AIGC技术在各个领域的应用三、AIGC技术对未来社会的影响四、AIGC技术的可能发展方向 前言 随着科技的飞速发展&#xff0c;人工智能与大数据的结合日益紧密&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;技术作为这一领域…

DRF 查询(排序、过滤、分页)

查询(排序、过滤、分页) 【0】准备 &#xff08;1&#xff09;Q查询 详细内容可见&#xff1a;Django模型层-CSDN博客Django 的 Q 对象提供了一种在数据库查询中构造复杂查询的方法。当你想在单个查询中组合多个过滤条件&#xff0c;并且这些条件之间不仅仅是简单的 AND 关系…

阿里云X魔搭社区Create@AI创客松第四届冠军:MumuLab

4月13日终于迎来了线下Demo Day&#xff0c;此前阿里云 X 魔搭社区 X Datawhale CreateAI创客松已经紧锣密鼓地准备了一个多月时间&#xff0c;全球150团队报名、创作出66作品、评选出25支团队进入决赛&#xff0c;作品范围覆盖从办公效率到法律调解再到游戏互动以及构建童话世…

构建交通新动脉 激活襄阳城市发展动力

—— 襄阳环线提速改造工程通车 即从巴峡穿巫峡,便下襄阳向洛阳。襄阳,这座位于汉江之滨的历史文化古城,自古便为交通要塞,正以崭新的姿态迈入一个新时代——城市交通快速化时代。4月26日,襄阳环线提速改造工程正式通车,“一轴三环九放射”城市骨架路网体系基本形成。它不仅是…

python 实现用户登录

1. JWT Token 参考&#xff1a;https://www.zhihu.com/question/364616467 jwt官网&#xff1a;https://jwt.io/#debugger-io 1.1. Token Token 是一个宽泛的术语&#xff0c;它可以指代任何一种用于身份验证的机制。Token 常常被用在验证和授权流程中。Token 可以有不同的形…