1、使用
ContextCompat.checkSelfPerSelfPermission()
检查权限状态2、获取的权限状态和
PackageManager.PERMISSION_GRANTEN
比较3、如果没有权限则向用户申请权限
ActivityCompat.requesPermissions()
4、重写
onRequestPermissionsResult
权限判断值:
运行时权限的核心在于从用户获取权限。
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//判断是否存在需要的权限,不存在则申请
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
} else {
call();
}
}
});
}
protected void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
//判断是否具有权限
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
//grantResults储存了用户的授权结果
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call();
}else {
Toast.makeText(this, "No Permission", Toast.LENGTH_SHORT).show();
}
}
}
}
ContextCompat.checkSelfPermission()
是一个用于检查权限状态的方法,它是Android支持库(Support Library)中的一个实用工具类。该方法用于在应用中检查特定权限是否已经被授予。**返回一个int值。**该方法获取两个参数,第一个是context,第二个是具体的权限名。
ActivityCompat.requestPermissions()
它会在运行时向用户请求权限。具有三个三个参数。
onRequestPermissionsResult()
方法中处理权限请求结果时,系统会提供这个请求码,帮助你识别是哪个权限请求的结果。)。onRequestPermissionsResult
该重写方法具有三个参数:
PackageManager.PERMISSION_GRANTED
,否则为PackageManager.PERMISSION_DENIED
。例如:包名为 com.example.permission,存在一张表table1
它的标准URI为 content://com.example.permission.provider/table1
得到标准URI以后需要解析为Uri对象来查询table表的数据。
Uri uri = Uri.parse("content://com.example.permission.provider/table1")
使用这个Uri进行查询的代码如下:
和SQLite的查询方式相似,我们还是获得一个Cursor对象,然后通过这个对象获取数据。
if(cursor != null){
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
}while (cursor.moveToNext());
}
ContentValues values = new ContentValues();
values.put("name","This is a book");
getContentResolver().insert(uri,values);
ContentValues values1 = new ContentValues();
values.put("name","This is a book");
getContentResolver().delete(uri,"name = ?",new String[]{"This is a b"});
ContentValues values1 = new ContentValues();
values.put("name","This is a book1");
getContentResolver().update(uri,values,"name = ?",new String[]{"This is a book"});
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
ArrayAdapter<String> adapter;
//保存联系人
List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,contactsList);
binding.contactsView.setAdapter(adapter);
if(ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},2);
}else {
ReadContacts();
}
}
private void ReadContacts(){
Cursor cursor = null;
try {
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,null,null);
if (cursor != null && cursor.moveToFirst()) {
int nameColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
int numberColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
do {
String name = cursor.getString(nameColumnIndex);
String number = cursor.getString(numberColumnIndex);
contactsList.add(name + " " +number);
} while (cursor.moveToNext());
adapter.notifyDataSetChanged();
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (cursor != null){
cursor.close();
}
}
}
//判断是否具有权限
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 2:
if(grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
ReadContacts();
}else {
Toast.makeText(this, "No Contacks Permission", Toast.LENGTH_SHORT).show();
}
}
}
}
注意在AndroidManifest中加入这句话获取系统权限。
通过新建一个自己的类,去继承ContentProvider
类。重写以下6个方法:
ContentResolver
的增删改查方法时传递过来的。而现在,我们需要对传入的Uri参数进行解析,从中分析出调用方期望访问的表和数据。UriMatcher
提供了一个addURI()
方法,这个方法接收三个参数,第一个参数是authority,第二个参数是path,第三个参数是指定id
所以,一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配table1表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/tablel/#
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);
UriMatcher.addURI("com.example.app.runtimepermissiontest","table1",TABLE1_DIR);
UriMatcher.addURI("com.example.app.runtimepermissiontest","table1/#",TABLE1_ITEM);
UriMatcher.addURI("com.example.app.runtimepermissiontest","table2",TABLE2_DIR);
UriMatcher.addURI("com.example.app.runtimepermissiontest","table1/#",TABLE2_ITEM);
}
将URI封装为对应的int参数。
为什么要放在static
中使用??
UriMatcher.NO_MATCH
是 UriMatcher
类的一个常量,它的值为 -1。在 UriMatcher
的构造函数中,如果传递了 UriMatcher.NO_MATCH
,表示创建一个空的 UriMatcher
对象,即不会匹配任何 URI。
使用方法样例如下:
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
switch (UriMatcher.match(uri)){
case TABLE1_DIR:
//查询表1的所有数据
break;
case TABLE1_ITEM:
//查询表1的单条数据
break;
}
return null;
}
它是所有的内容提供器都必须提供的一个方法,用于获取URI对象所对应的MIME
类型。一个内容URI所对应的MIME字符串主要由3部分组成。
例如:content://com.example.app.provider/table1这个内容的URI,对应的MIME为:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
@Nullable
@Override
public String getType(@NonNull 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.tablel"
}
return null;
}
这就是创建一个contentProvier的基本步骤。
重写继承ContenProvider
的方法:
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int GATEGORY_DIR = 2;
public static final int GATEGORY_ITEM = 3;
public static final String AUTHORITY = "com.example.sqlite.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbhelper;
//使用urimather封装URI
static {
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", GATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", GATEGORY_ITEM);
}
@Override
public boolean onCreate() {
//创建一个数据库帮助类
dbhelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbhelper.getWritableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("BooK", projection, selection,
selectionArgs, null, null, sortOrder);
break;
case BOOK_ITEM:
String BookID = uri.getPathSegments().get(1);
cursor = db.query("Book", projection, "id = ?",
new String[]{BookID}, null, null, sortOrder);
break;
case GATEGORY_DIR:
cursor = db.query("Category", projection, selection,
selectionArgs, null, null, sortOrder);
break;
case GATEGORY_ITEM:
String GatId = uri.getPathSegments().get(1);
cursor = db.query("Category", projection, "id = ?", new String[]{GatId}, null, null, sortOrder);
break;
}
return cursor;
}
先获取SQLiteDatanase的实例,然后通过实例查询数据。
当访问单条数据时,使用了getPathSegments,它会将内容URI权限只后的部分以**/**分开,将分割的结果放到一个字符串列表中,列表的0位置就是存放的路径,列表1位置就是存放的id了。然后通过selection
和selectionArgs
进行约束。
为什么返回 Cursor
对象而不是直接返回查询结果数据呢?
这是因为查询结果可能非常大,直接返回数据会导致内存占用过高,而 Cursor
使用了懒加载的机制,只在需要数据时才去获取,有效地减少了内存消耗。同时,使用 Cursor
还可以进行异步查询,避免阻塞主线程。
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbhelper.getWritableDatabase();
Uri uri1 = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);
uri1 = Uri.parse("Content://" + AUTHORITY + "/book/" + newBookId);
break;
case GATEGORY_DIR:
case GATEGORY_ITEM:
long newCategory = db.insert("Category", null, values);
uri1 = Uri.parse("Content://" + AUTHORITY + "/category/" + newCategory);
break;
}
return uri1;
}
向名为 “Book” 的数据库表中插入一条新的数据,并将插入后的新行的主键值或行号拼接到一个 Uri 中,以便返回给调用者。这个 Uri 包含了内容提供器的授权信息、表名和新行的主键值或行号等信息。
在一些情况下,客户端可能不需要获取到新插入数据的Uri
,或者内容提供器的设计不需要返回新插入数据的Uri
,那么这个步骤可以省略。但是,如果需要引用到新插入的数据,将其添加到Uri
是一个很常见的做法。
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase dp = dbhelper.getWritableDatabase();
int updatarows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
updatarows = dp.update("Book",values,selection,selectionArgs);
break;
case BOOK_ITEM:
String BookId = uri.getPathSegments().get(1);
updatarows = dp.update("Book",values,"id = ?",new String[]{BookId});
break;
case GATEGORY_DIR:
updatarows = dp.update("Category",values,selection,selectionArgs);
break;
case GATEGORY_ITEM:
String CategoryId = uri.getPathSegments().get(1);
updatarows = dp.update("Category",values,"id = ?",new String[]{CategoryId});
break;
}
return updatarows;
}
返回受到更新的行数位置。
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase dp = dbhelper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deletedRows = dp.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String BookId = uri.getPathSegments().get(1);
deletedRows = dp.delete("Book", "id = ?", new String[]{BookId});
break;
case GATEGORY_DIR:
deletedRows = dp.delete("Categroy", selection, selectionArgs);
break;
case GATEGORY_ITEM:
String CategoryId = uri.getPathSegments().get(1);
deletedRows = dp.delete("Category", "id = ?", new String[]{CategoryId});
break;
}
return deletedRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.sqlite.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.sqlite.provider.book";
case GATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.sqlite.provider.category";
case GATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.sqlite.provider.category";
}
return null;
}
新建一个项目ententprovidertest用于访问刚才的
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//添加数据
binding.add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Uri uri = Uri.parse("content://com.example.sqlite.provider/book");
ContentValues values =new ContentValues();
//将数据保存起来,第二条数据
values.put("name","two book");
values.put("author","dow1");
values.put("pages",2321);
values.put("price",1212);
Uri newUri = getContentResolver().insert(uri,values);
newId = newUri.getPathSegments().get(1);
}
});
//查询数据
binding.query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Uri uri = Uri.parse("content://com.example.sqlite.provider/book");
Cursor cursor = getContentResolver().query(uri,null,null,null);
if(cursor != null && cursor.moveToFirst()){
do {
Log.d("ACTIVITY1",cursor.getString((int)cursor.getColumnIndex("name")));
Log.d("ACTIVITY1",cursor.getString((int)cursor.getColumnIndex("author")));
Log.d("ACTIVITY1",cursor.getString((int)cursor.getColumnIndex("pages")));
Log.d("ACTIVITY1",cursor.getString((int)cursor.getColumnIndex("price")));
}while (cursor.moveToNext());
cursor.close();
}
}
});
//更新数据
binding.Update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Uri uri = Uri.parse("content://com.example.sqlite.provider/book/"+newId);
ContentValues values = new ContentValues();
values.put("name","three book");
values.put("pages",123);
values.put("price",20.5);
getContentResolver().update(uri,values,null,null);
}
});
//删除数据
binding.Delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Uri uri = Uri.parse("content://com.example.sqlite.provider/book/"+newId);
getContentResolver().delete(uri,null,null);
}
});
}
}
Android 11(API级别30)以及更高版本, 更改了应用查询用户已在设备上安装的其他应用以及与之交互的方式。使用
元素,应用可以定义一组自身可访问的其他软件包。
如果您的应用以 Android 11 或更高版本为目标平台,您可能需要在应用的清单文件中添加
元素。在
元素中,您可以按软件包名称、intent 签名或提供程序授权指定软件包。
所以现在新的应用ententprovidertest
是无法访问刚刚实现ContenProvider
应用名为sqlite
的应用,我们需要指定sqlite
的软件包名:
<queries>
<package android:name="com.example.sqlite" />
queries>
这样我们使用ententprovidertest,就可以操作刚刚实现ContenProvider
的应用的数据库。
如果您需要查询 Content Provider 但不知道具体的软件包名称,您可以在
元素中声明该提供程序授权,假设我们不知道sqlite
应用的包名,我们只知道它包含一个内容提供者:
<provider
android:name=".DatabaseProvider"
android:authorities="com.example.sqlite.provider"
android:enabled="true"
android:exported="true">provider>
我们需要在ententprovidertest
应用中查询是否存在这个sqlite
应用的authorities
。在ententprovidertest
应用的AndroidManifest中加入这段代码:
<queries>
<provider android:authorities="com.example.sqlite.provider"/>
queries>
这样,ententprovidertest
应用就可以在运行时查询设备上是否安装了名为 sqlite
的应用,并可以访问其内容提供者 com.example.sqlite.provider
提供的数据。
请注意,如果在单个
元素中需要声明多个提供者授权,您可以使用以英文分号分隔的方式来列出它们,如下所示:
<queries>
<provider android:authorities="com.example.sqlite.provider; com.example.otherprovider" />
queries>
或者,您也可以使用多个
元素,将它们全部放在同一个
元素中,如下所示:
<queries>
<provider android:authorities="com.example.sqlite.provider" />
<provider android:authorities="com.example.otherprovider" />
queries>
在极少数情况下,您的应用可能需要查询设备上的所有已安装应用或与之交互,不管这些应用包含哪些组件。为了允许您的应用看到其他所有已安装应用,系统会提供 QUERY_ALL_PACKAGES
权限。它允许应用查询设备上所有已安装应用的信息,包括它们的包名、版本。
下面列出了适合添加 QUERY_ALL_PACKAGES
权限的用例的一些示例:
在ententprovidertest
应用的AndroidManifest中加上:
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
QUERY_ALL_PACKAGES
权限:允许查询设备上的任何正常应用程序,无论清单声明如何。
防护级别:普通