【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://xiahunao.cn/news/2981082.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

STM32的GPIO输入和输出函数详解

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. GPIO模式 2. GPIO输出 2.1 RCC 2.2 GPIO 3. 代码示例 3.1 RCC时钟 3.2 GPIO初始化 3.3 GPIO输出函数 3.4 推挽输出和开漏输出 4. GPIO输入 4.1 输入模式 4.2 数据读取函数 5. C语言语法 1…

【书生浦语第二期实战营学习笔记作业(四)】

课程文档&#xff1a;https://github.com/InternLM/Tutorial/blob/camp2/xtuner/readme.md 作业文档&#xff1a;https://github.com/InternLM/Tutorial/blob/camp2/xtuner/homework.md 书生浦语第二期实战营学习笔记&作业(四) 1.1、微调理论讲解及 XTuner 介绍 两种Fin…

8.4.3 使用3:配置单臂路由实现VLAN间路由

1、实验目的 通过本实验可以掌握&#xff1a; 路由器以太网接口上的子接口配置和调试方法。单臂路由实现 VLAN间路由的配置和调试方法。 2、实验拓扑 实验拓扑如下图所示。 3、实验步骤 &#xff08;1&#xff09;配置交换机S1 S1(config)#vlan 2 S1(config-vlan)#exit S…

Vue基于高德地图API封装一个地图组件

一、参考资料 高德开放平台 | 高德地图API (amap.com) 二、安装及配置 pnpm i vuemap/vue-amap --save man.ts 密钥及安全密钥需要自己到高德地图开放平台控制台获取. import { createApp } from vue import App from ./App.vue import router from ./router i…

蓝桥杯管道

一开始拿到这道题没有什么头绪。综合各路大佬题解&#xff0c;一下子豁然开朗。 题眼&#xff1a;每一段感受器都感受到水的最早时间。由于整个管道&#xff0c;分为多个段&#xff0c;每个段都有一个感受器。所以题眼翻译为&#xff1a;覆盖满整条管道&#xff0c;所需要的最短…

系统设计 --- E2E Test System

系统设计 --- E2E Test System 什么是E2EE2E Architecture Example 什么是E2E E2E&#xff08;端到端&#xff09;测试是一种软件测试方法&#xff0c;旨在模拟真实的用户场景&#xff0c;测试整个应用程序或系统的端到端功能和交互流程。E2E 测试涵盖了从用户界面到后端系统的…

用于车载T-BOX汽车级的RA8900CE

用于车载T-BOX等高精度计时的汽车级时钟模块RTC:RA8900CE.车载实时时钟芯片RA8900CE内置32.768Khz的晶体&#xff0c;实现年、月、日、星期、小时、分钟和秒精准计时。RA8900CE满足AEC-Q200认证&#xff0c;内置温补功能&#xff0c;保证实时时钟的稳定可靠&#xff0c;功耗低至…

计算机网络3——数据链路层3以太网的MAC层

文章目录 一、MAC 层的硬件地址1、介绍2、注意点3、定制标准 二、MAC 帧的格式1、结构2、工作原理3、其他 一、MAC 层的硬件地址 1、介绍 在局域网中&#xff0c;硬件地址又称为物理地址或 MAC地址(因为这种地址用在MAC帧中)。 大家知道&#xff0c;在所有计算机系统的设计中…

[C++][算法基础]能被整除的数(容斥原理)

给定一个整数 &#x1d45b; 和 &#x1d45a; 个不同的质数 &#x1d45d;1,&#x1d45d;2,…,&#x1d45d;&#x1d45a;。 请你求出 1∼&#x1d45b; 中能被 &#x1d45d;1,&#x1d45d;2,…,&#x1d45d;&#x1d45a; 中的至少一个数整除的整数有多少个。 输入格式…

Linux报错处理:‘abrt-cli status’ timed out

最近登录服务器时出现报错&#xff0c;后来查阅资料发现是因为ssh登录时间很久&#xff0c;登录后出现abrt-cli status timed out 的报错。 1.问题分析 abrt-cli是ABRT(Automated Bug Reporting Tool)的命令行接口&#xff0c;用于在Linux系统中处理和报告程序崩溃。 如果abr…

【Java--数据结构】“从扑克到程序:深入探讨洗牌算法的原理与魅力“

前言 以下是学习Java顺序表的一个实例应用———简单的洗牌算法。 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 前言 定义每张扑克牌的属性 生成一副扑克牌&#xff08;不包含大小王&#xff09; 洗牌方法 发牌方…

软件测试之【软件测试概论二】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言软件测试模型瀑布模型V模型W&#xff08;双V&#xff09;模型测试活动 软…

ElasticSearch总结二

正向索引和倒排索引&#xff1a; 正向索引&#xff1a; 比方说我这里有一张数据库表&#xff0c;那我们知道对于数据库它一般情况下都会基于i d去创建一个索引&#xff0c;然后形成一个b树。 那么你根据i d进行检索的速度&#xff0c;就会非常的快&#xff0c;那么这种方式的…

(N-151)基于微信小程序校园学生活动管理平台

开发工具&#xff1a;IDEA、微信小程序 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 前端技术&#xff1a;vue、uniapp 服务端技术&#xff1a;springbootmybatisplus 本系统分微信小程序和管理后台两部分&am…

吴恩达深度学习笔记:深度学习的 实践层面 (Practical aspects of Deep Learning)1.6-1.8

目录 第一门课&#xff1a;第二门课 改善深层神经网络&#xff1a;超参数调试、正 则 化 以 及 优 化 (Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)第一周&#xff1a;深度学习的 实践层面 (Practical aspects of Deep Learning)…

【六十】【算法分析与设计】用一道题目解决dfs深度优先遍历,dfs中节点信息,dfs递归函数模板进入前维护出去前回溯,唯一解的剪枝飞升返回值true

路径之谜 题目描述 小明冒充X星球的骑士,进入了一个奇怪的城堡。 城堡里边什么都没有,只有方形石头铺成的地面。 假设城堡地面是nn个方格。如下图所示。 按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着音走,也不能跳跃。每走到一个新方格,就要向正北 方和正西…

跨部门协作中的沟通困境与平台建设策略——以软硬件研发为例

一、背景 在科技行业&#xff0c;跨部门合作的重要性不言而喻&#xff0c;然而实际工作中&#xff0c;经常会遭遇沟通不畅的现象。以软件与硬件研发部门为例&#xff0c;两者在产品研发过程中经常需要紧密协作&#xff0c;但却时常出现信息传递障碍。当你试图阐述观点时&#…

01、创建型-单例模式--只有一个实例

文章目录 前言一、基本介绍1.1 什么是单例模式1.2 为什么要用单例模式1.3 应用场景1.4 单例优缺点 二、单例模式的实现方式2.1 饿汉式单例2.1.1 静态变量方式2.1.2 静态代码块 2.2 懒汉式单例2.2.1 懒汉式单例2.2.2 懒汉式优化①-线程安全2.2.2 懒汉式优化②-双重检查锁2.2.3 懒…

图书租赁系统-扣费服务

resources中添加moment.js文件。 然后引入moment.js文件&#xff1a; <script src"/js/moment.js"></script>借阅结束时间选完后changeDate事件&#xff1a; $("input[nameendTime]").datetimepicker({format: "yyyy-mm-dd hh:ii",…

分享基于鸿蒙OpenHarmony的Unity团结引擎应用开发赛

该赛题旨在鼓励更多开发者基于OpenHarmony4.x版本&#xff0c;使用团结引擎创造出精彩的游戏与应用。本次大赛分为“创新游戏”与“创新3D 化应用”两大赛道&#xff0c;每赛道又分“大众组”与“高校组”&#xff0c;让不同背景的开发者同台竞技。无论你是游戏开发者&#xff…