安卓中大概分为五种数据存储方式
- SharedPrefences;
- SQLite数据库
- 文件存储
- 网络存储
- ContentProvider
1、共享参数SharedPrefences
1.1共享参数的简介
SharedPrefences是Android的一个轻量级存储工具,它采用的存储结构是Key-Value的键值对方式类似于java中的Properties,SharedPrefences将Key-Value的键值对保存到配置文件当中,存储的介质时一个XML文件,以XML标记保存键值对。保存
保存共享参数键值对信息的文件路径为/data/data/应用包名/shared_prefs/文件名.xml。
例:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="state">学习中</string>
</map>
基于XML格式的特点,共享参数的只要应用场合:
① 简单且孤立的数据。若是复杂且相互关联的数据,则要保存到关系型数据库。
②文本形式的数据。若是二进制数据,则要保存至文件。
③需要持久化存储的数据。App的个性化配置信息、用户使用App的行为信息、临时需要保存的片段信息等。
1.2共享参数的使用
①使用getSharedPrefernces方法可以获得共享参数实例
SharedPreferences share = getSharedPreferences("share",MODE_PRIVATE);
注:第一个参数表示参数的文件名,第二个参数表示操作模式,MODE_PRIVATE表示私有模式
②往共享参数存出数据,需要借助Editor类
SharedPreferences.Editor editor = shared.edit();
editor.putString("text",text);
//提交编辑器修改
editor.commit();
③从共享参数中读取数据
SharedPreferences shared1 = getSharedPreferences("share",MODE_PRIVATE);
String result = shared1.getString("text","");
案例代码:
xml部分:
<?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=".demo7"
android:orientation="vertical">
<EditText
android:id="@+id/et_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入一段悄悄话"/>
<Button
android:id="@+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存共享参数"/>
<Button
android:id="@+id/btn_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="显示共享参数"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/black">
</TextView>
</LinearLayout>
java部分:
package com.example.aclass;
import androidx.appcompat.app.AppCompatActivity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
//共享参数SharedPreferences 存储数据
public class demo7 extends AppCompatActivity implements View.OnClickListener {
private EditText et_text;
private Button btn_submit;
private Button btn_view;
private TextView tv_result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo7);
//获取输入文本
et_text = findViewById(R.id.et_text);
//获取提交共享参数按钮
btn_submit =findViewById(R.id.btn_submit);
//获取读取共享参数按钮
btn_view = findViewById(R.id.btn_view);
//获取显示文本视图
tv_result = findViewById(R.id.tv_result);
//给提交按钮注册事件
btn_submit.setOnClickListener(this);
//给读取按钮注册事件
btn_view.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_submit:
//得到输入文本
String text = et_text.getText().toString();
//使用getSharedPrefernces方法可以获得共享参数实例
//第一个参数表示参数的文件名,第二个参数表示操作模式,MODE_PRIVATE表示私有模式
System.out.println(1);
SharedPreferences shared = getSharedPreferences("share",MODE_PRIVATE);
//往共享参数存出数据,需要借助Editor类
SharedPreferences.Editor editor = shared.edit();
editor.putString("text",text);
//提交编辑器修改
editor.commit();
break;
case R.id.btn_view:
//也可以将共享参数声明为全局
SharedPreferences shared1 = getSharedPreferences("share",MODE_PRIVATE);
String result = shared1.getString("text","");
//将结果显示到结果集中
tv_result.setText(result);
break;
}
}
}
2、SQLite数据库
待补充
3、文件存储
3.1 私有空间和公共存储空间
为了更规范地管理手机存储空间,
Android
从
7.0
开始将存储卡划分为私有存储和公共存储两大部分,也就是分区存储方式,
![](https://img-blog.csdnimg.cn/b569cba8640949339c245dcdf5e8481d.png)
系统给每个App
都分配了默认的私有存储空间。
App
在私有空间上读写文件无须任何授权。
但是若想在公共空间读写文件,则要在AndroidManifest.xml里面添加下述的权限配置。
<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAG" />
安卓10以后还需要在application中添加
android:requestLegacyExternalStorage="true"
但是即使
App
声明了完整的存储卡操作权限,系统仍然默认禁止该App访问公共空间。打开手机的系统设置界面,进入到具体应用的管理页面,会发现该应用的存储访问权限被禁止了。
![](https://img-blog.csdnimg.cn/5d55a1a69e68425383adc388d6d4b899.png)
当然图示的禁止访问只是不让访问存储卡的公共空间,
App
自身的私有空间依旧可以正常读写。这缘于Android把存储卡分成了两块区域,一块是所有应用均可访问的公共空间,另一块是只有应用自己才可访问的专享空间。虽然Android
给每个应用都分配了单独的安装目录,但是安装目录的空间很紧张,所以Android
在存储卡的
“Android/data”
目录下给每个应用又单独建了一个文件目录,用来保存应用自己需要处理的临时文件。这个目录只有当前应用才能够读写文件,其他应用是不允许读写的。由于私有空间本身已经加了访问权限控制,因此它不受系统禁止访问的影响,应用操作自己的文件目录自然不成问题。因为私有的文件目录只有属主应用才能访问,所以一旦属主应用被卸载,那么对应的目录也会被删掉。
3.2存储卡上读写文本文件
① 写一个文件读写的类 FileUtil:
package com.example.aclass.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileUtil {
//把字符串保存到指定的文本文件
public static void saveText(String path ,String txt){
BufferedWriter os = null;
try {
os = new BufferedWriter(new FileWriter(path));
os.write(txt);
}catch (Exception e){
e.printStackTrace();
}finally {
if(os!=null){
try {
os.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
//从指定路径的文本文件中读取内容字符串
public static String openText(String path){
BufferedReader is = null;
StringBuilder sb = new StringBuilder();
try {
is = new BufferedReader(new FileReader(path));
String line =null;
while ((line = is.readLine())!=null){
sb.append(line);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(is!=null){
try {
is.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
return sb.toString();
}
//把位图数据保存到指定路径的图片文件
public static void saImage(String path, Bitmap bitmap) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path);
//把位图数据压缩到文件输出流中
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
}catch (Exception e){
e.printStackTrace();
}finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//从指定路径的图片文件读取位图数据
public static Bitmap openImage(String path) {
Bitmap bitmap = null;
FileInputStream fis = null;
try {
fis =new FileInputStream(path);
bitmap = BitmapFactory.decodeStream(fis);
}catch (Exception e){
e.printStackTrace();
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
}
ToastUtil:
package com.example.aclass.util;
import android.content.Context;
import android.widget.Toast;
public class ToastUtil {
public static void show(Context ctx, String desc) {
Toast.makeText(ctx, desc, Toast.LENGTH_SHORT).show();
}
}
②布局文件
<?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=".demo8"
android:orientation="vertical">
<EditText
android:id="@+id/et_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入一段悄悄话"/>
<Button
android:id="@+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存"/>
<Button
android:id="@+id/btn_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读取"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/black">
</TextView>
</LinearLayout>
③ Java
1) 外部存储私有空间
//外部存储的私有空间
directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
2) 外部存储公共空间
directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
3)内部存储的私有空间
directory = getFilesDir().toString();
案例代码:
package com.example.aclass;
import androidx.appcompat.app.AppCompatActivity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.example.aclass.util.FileUtil;
import com.example.aclass.util.ToastUtil;
import java.io.File;
//共享参数SharedPreferences 存储数据
public class demo8 extends AppCompatActivity implements View.OnClickListener {
private EditText et_text;
private Button btn_submit;
private Button btn_view;
private TextView tv_result;
private String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo8);
//获取输入文本
et_text = findViewById(R.id.et_text);
//获取提交按钮
btn_submit =findViewById(R.id.btn_submit);
//获取读取按钮
btn_view = findViewById(R.id.btn_view);
//获取显示文本视图
tv_result = findViewById(R.id.tv_result);
//给提交按钮注册事件
btn_submit.setOnClickListener(this);
//给读取按钮注册事件
btn_view.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_submit:
String text = et_text.getText().toString();
//保存的文件名称
String fileName = System.currentTimeMillis() + ".txt";
String directory = null;
//外部存储的私有空间
directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
//外部存储的公共空间
directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
//内部存储的私有空间
directory = getFilesDir().toString();
//文件的完整路径
path = directory+ File.separatorChar+fileName;
System.out.println("保存路径为:"+path);
FileUtil.saveText(path,text);
ToastUtil.show(this,"保存成功");
break;
case R.id.btn_view:
tv_result.setText(FileUtil.openText(path));
break;
}
}
}
3.2存储卡上读写图片文件
① FileUtil,ToastUil使用上面写到的文件
②xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存"/>
<Button
android:id="@+id/btn_read"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读取"/>
<ImageView
android:id="@+id/iv_content"
android:layout_width="match_parent"
android:layout_height="400dp"
android:scaleType="fitCenter"/>
</LinearLayout>
③java代码
package com.example.aclass;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.aclass.util.FileUtil;
import com.example.aclass.util.ToastUtil;
import java.io.File;
public class ImageWriteActivity extends AppCompatActivity implements View.OnClickListener {
private ImageView iv_content;
private String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_write);
iv_content = findViewById(R.id.iv_content);
findViewById(R.id.btn_save).setOnClickListener(this);
findViewById(R.id.btn_read).setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_save:
String fileName = System.currentTimeMillis()+".jpeg";
//获取当前APP的私有下载目录
path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString()+ File.separatorChar +fileName;
Log.d("ning",path);
//从指定的资源文件中获取位图对象
Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.yellow);
FileUtil.saImage(path,b1);
ToastUtil.show(this,"保存成功");
break;
case R.id.btn_read:
//方式1
// Bitmap b2 = FileUtil.openImage(path);
// iv_content.setImageBitmap(b2);
//方式2
// Bitmap b2 = BitmapFactory.decodeFile(path);
// iv_content.setImageBitmap(b2);
//方式3
iv_content.setImageURI(Uri.parse(path));
break;
}
}
}
④ 示例
4、网络存储
待补充
5、ContentProvider
待补充
6、安卓权限
Android
系统为了防止某些
App
滥用权限,从
6.0
开始引入了运行时权限管理机制,允许
A
pp
在运行过程中动态检查是否拥有某项权限,一旦发现缺少某种必需的权限,则系统会
自动弹出小窗提示用户去开启该权限。
![](https://img-blog.csdnimg.cn/dc52798cadea42c4a474cfa9b4f94e6e.png)
6.1 运行时动态申请权限
在4中讲到App
若想访问存储卡的公共空间,就要在 AndroidManifest.xml里面添加下述的权限配置。
<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAG" />
然而即使
App
声明了完整的存储卡操作权限,从
Android 7.0
开始,系统仍然默认禁止该
App
访问公共空间,必须到设置界面手动开启应用的存储卡权限才行。尽管此举是为用户隐私着想,可是人家咋知道要手工开权限呢?就算用户知道,去设置界面找到权限开关也颇费周折。为此Android
支持在
Java
代码中处理权限,处理过程分为3
个步骤,详述如下:
① 检查
App
是否开启了指定权限
权限检查需要调用
ContextCompat
的
checkSelfPermission
方法,该方法的第一个参数为活动实例,第二个参数为待检查的权限名称,例如存储卡的写权限名为 Manifest.permission.WRITE_EXTERNAL_STORAGE。注意
checkSelfPermission
方法的返回值,当它为PackageManager.PERMISSION_GRANTED时表示已经授权,否则就是未获授权。
② 请求系统弹窗,以便用户选择是否开启权限
一旦发现某个权限尚未开启,就得弹窗提示用户手工开启,这个弹窗不是开发者自己写的提醒对话框,而是系统专门用于权限申请的对话框。调用ActivityCompat
的
requestPermissions
方法,即可命令系统自动弹出权限申请窗口,该方法的第一个参数为活动实例,第二个参数为待申请的权限名称数组,第三个参数为本次操作的请求代码。
③ 判断用户的权限选择结果
然而上面第二步的
requestPermissions
方法没有返回值,那怎么判断用户到底选了开启权限还是拒绝权限呢?其实活动页面提供了权限选择的回调方法onRequestPermissionsResult
,如果当前页面请求弹出权限申请窗口,那么该页面的Java
代码必须重写
onRequestPermissionsResult
方法,并在该方法内部处理用户的权限选择结果。
6.2 具体使用步骤
①定义用户使用的权限
例如:
public static final String[] PERMISSION_CONTACTS = new String[]{
//操作通讯录的读写权限
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
};
public static final String[] PERMISSION_SMS = new String[]{
//操作短信的读写权限
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
②定义权限标识符
//标识请求的权限类型
//通讯录读写的权限标识
private static final int REQUEST_CODE_CONTACTS = 1;
//发送短信的权限标识
private static final int REQUEST_CODE_SMS = 2;
③定义一个获取权限的工具类PermissionUtil
传递的三个参数 :
Activity act:指定activity
String[] permissions:权限的类型
int requestCode:权限的标识符 (不可以为负数也不能大于2的16次方即65536)
String[] permissions:权限的类型
int requestCode:权限的标识符 (不可以为负数也不能大于2的16次方即65536)
package com.example.aclass.util;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PermissionUtil {
/*
* Activity act:指定activity
* String[] permissions:权限的类型
* int requestCode:权限的标识符
* */
// 检查多个权限。返回true表示已完全启用权限,返回false表示未完全启用权限
public static boolean checkPermission(Activity act, String[] permissions, int requestCode) {
// Android 6.0 之后开始采用动态权限管理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
/*通过看源码
public static final int PERMISSION_DENIED = -1;
public static final int PERMISSION_GRANTED = 0;
0代表全部授权 -1表示未授权
*
* */
int check = PackageManager.PERMISSION_GRANTED;
for (String permission : permissions) {
//判断权限是否授权
check = ContextCompat.checkSelfPermission(act, permission);
if (check != PackageManager.PERMISSION_GRANTED) {
break;
}
}
// 未开启该权限,则请求系统弹窗,好让用户选择是否立即开启权限
if (check != PackageManager.PERMISSION_GRANTED) {
//弹窗提示要获取权限
ActivityCompat.requestPermissions(act, permissions, requestCode);
return false;
}
}
return true;
}
// 检查权限结果数组,返回true表示都已经获得授权。返回false表示至少有一个未获得授权
public static boolean checkGrant(int[] grantResults) {
if (grantResults != null) {
// 遍历权限结果数组中的每条选择结果
for (int grant : grantResults) {
// 未获得授权
if (grant != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
return false;
}
}
注意到上面代码有判断安卓版本号,只有系统版本大于
Android 6.0
(版本代号为
M
),才执行后续的权限校验操作。这是因为从Android 6.0
开始引入了运行时权限机制,在
Android 6.0
之前,只要
App
在AndroidManifest.xml中添加了权限配置,则系统会自动给
App
开启相关权限;但在
Android 6.0
之后, 即便事先添加了权限配置,系统也不会自动开启权限,而要开发者在App
运行时判断权限的开关情况,再据此动态申请未获授权的权限。
④获取权限逻辑操作
两种形式:
懒汉式:当需要权限是才进行权限的判断
饿汉式:一进入activity就
进行权限的判断
代码:
懒汉式:
package com.example.aclass;
import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.example.aclass.util.PermissionUtil;
import com.example.aclass.util.ToastUtil;
public class PermissionLazyActivity extends AppCompatActivity implements View.OnClickListener {
//定义用户需要的权限
public static final String[] PERMISSION_CONTACTS = new String[]{
//操作通讯录的读写权限
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
};
public static final String[] PERMISSION_SMS = new String[]{
//操作短信的读写权限
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
//标识请求的权限类型
//通讯录读写的权限标识
private static final int REQUEST_CODE_CONTACTS = 1;
//发送短信的权限标识
private static final int REQUEST_CODE_SMS = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_lazy);
findViewById(R.id.btn_contact).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_contact:
PermissionUtil.checkPermission(this,PERMISSION_CONTACTS,REQUEST_CODE_CONTACTS);
break;
case R.id.btn_sms:
PermissionUtil.checkPermission(this,PERMISSION_SMS,REQUEST_CODE_SMS);
break;
}
}
//判断权限是否获取到了
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case REQUEST_CODE_CONTACTS:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("ning","通讯录权限获取成功");
}else {
ToastUtil.show(this,"获取通讯录读写权限失败!");
jumpToSetting();
}
break;
case REQUEST_CODE_SMS:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("ning","收发短信权限获取成功");
}else {
ToastUtil.show(this,"获取收发短信权限失败!");
jumpToSetting();
}
break;
}
}
//跳转到应用设置界面
private void jumpToSetting(){
Intent intent = new Intent();
//进入应用详情的设置界面
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
//指定那个应用 通过包名
intent.setData(Uri.fromParts("package",getPackageName(),null));
//使用新的任务栈运行
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
饿汉式:
package com.example.aclass;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.example.aclass.util.PermissionUtil;
import com.example.aclass.util.ToastUtil;
public class PermissionHungryActivity extends AppCompatActivity implements View.OnClickListener {
public static final String[] PERMISSIONS = new String[]{
//操作通讯录的读写权限
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
//操作短信的读写权限
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
private static final int REQUEST_CODE_ALL = 1;
private static final int REQUEST_CODE_CONTACTS = 2;
private static final int REQUEST_CODE_SMS = 3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_lazy);
findViewById(R.id.btn_contact).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
PermissionUtil.checkPermission(this,PERMISSIONS,REQUEST_CODE_ALL);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_contact:
PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[0],PERMISSIONS[1]},REQUEST_CODE_CONTACTS);
break;
case R.id.btn_sms:
PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[2],PERMISSIONS[3]},REQUEST_CODE_SMS);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case REQUEST_CODE_ALL:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("ning","所有权限获取成功");
}else {
//部分权限获取失败
for(int i = 0 ; i<grantResults.length;i++){
if(grantResults[i] != PackageManager.PERMISSION_GRANTED){
//判断是什么权限没有获取成功
switch (permissions[i]){
case Manifest.permission.READ_CONTACTS:
case Manifest.permission.WRITE_CONTACTS:
ToastUtil.show(this,"获取通讯录读写权限失败!");
jumpToSetting();
return;
case Manifest.permission.SEND_SMS:
case Manifest.permission.READ_SMS:
ToastUtil.show(this,"获取收发短信权限失败!");
jumpToSetting();
return;
}
}
}
}
break;
case REQUEST_CODE_CONTACTS:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("ning","通讯录权限获取成功");
}else {
ToastUtil.show(this,"获取通讯录读写权限失败!");
jumpToSetting();
}
break;
case REQUEST_CODE_SMS:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("ning","收发短信权限获取成功");
}else {
ToastUtil.show(this,"获取收发短信权限失败!");
jumpToSetting();
}
break;
}
}
//跳转到应用设置界面
private void jumpToSetting(){
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package",getPackageName(),null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
⑤ 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PermissionLazyActivity"
android:orientation="vertical">
<Button
android:id="@+id/btn_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读写通讯录"/>
<Button
android:id="@+id/btn_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="收发短信"/>
</LinearLayout>
点击弹窗上的
“
始终允许
”
,表示同意赋予存储卡读写权限,然后系统自动给
App
开启了存储卡权限,并执
行后续处理逻辑,也就是跳到了
FileWriteActivity
页面,在该页面即可访问公共空间的文件了。但在
Android 10
系统中,即使授权通过,
App
仍然无法访问公共空间,这是因为
Android 10
默认开启沙箱模
式,不允许直接使用公共空间的文件路径,此时要修改
AndroidManifest.xml
,给
application
节点添加
如下的
requestLegacyExternalStorage
属性:
android:requestLegacyExternalStorage="true"
从
Android 11
开始,为了让应用升级时也能正常访问公共空间,还得修改
AndroidManifest.xml
,给
application
节点添加如下的
preserveLegacyExternalStorage
属性,表示暂时关闭沙箱模式:
android:preserveLegacyExternalStorage="true"
附:其他权限