一、如何选择buildToolVersion,compileSdkVersion, minSdkVersion, targetSdkVersion和Support Library
首先我们要明确如下几个知识点,然后再说结论:
1、compileSdkVersion是你SDK的版本号,也就是API Level,例如API-19、API-20、API-21等等。
2、buildeToolVersion是你构建工具的版本,其中包括了打包工具aapt、dx等等。这个工具的目录位于..your_sdk_path/build-tools/XX.XX.XX,这个版本号一般是API-LEVEL.0.0。 例如I/O2014大会上发布了API20对应的build-tool的版本就是20.0.0,在这之间可能有小版本,例如20.0.1等等。例如,我本地sdk目录下面的内容如下:
3、你可以用高版本的build-tool去构建一个低版本的sdk工程,例如build-tool的版本为20,去构建一个sdk版本为18的,例如:compileSdkVersion 18 , buildToolsVersion "22.0.1"这样也是OK的。
4、compileSdkVersion 告诉 Gradle 用哪个 Android SDK 版本编译你的应用。使用任何新添加的 API 就需要使用对应 Level 的 Android SDK。
5、需要强调的是修改 compileSdkVersion 不会改变运行时的行为。当你修改了 compileSdkVersion 的时候,可能会出现新的编译警告、编译错误,但新的 compileSdkVersion 不会被包含到 APK 中:它纯粹只是在编译的时候使用。(你真的应该修复这些警告,他们的出现一定是有原因的)
6、因此我们强烈推荐总是使用最新的 SDK 进行编译。在现有代码上使用新的编译检查可以获得很多好处,避免新弃用的 API ,并且为使用新的 API 做好准备。
7、注意,如果使用 Support Library,那么使用最新发布的 Support Library 就需要使用最新的 SDK 编译。例如,要使用 23.1.1 版本的 Support Library ,compileSdkVersion 就必需至少是 23 (大版本号要一致!)。通常,新版的 Support Library 随着新的系统版本而发布,它为系统新增加的 API 和新特性提供兼容性支持。
8、targetSdkVersion 是 Android 提供向前兼容的主要依据,在应用的 targetSdkVersion 没有更新之前系统不会应用最新的行为变化。也就是说随着 Android 系统的升级,某个系统的 API 或者模块的行为可能会发生改变,但是为了保证老 APK 的行为还是和以前兼容。只要 APK 的 targetSdkVersion 不变,即使这个 APK 安装在新 Android 系统上,其行为还是保持老的系统上的行为,这样就保证了系统对老应用的前向兼容性。
9、如果 compileSdkVersion 设置为可用的最新 API,那么 minSdkVersion 则是应用可以运行的最低要求。minSdkVersion 是 Google Play 商店用来判断用户设备是否可以安装某个应用的标志之一。举个例子来说,当我项目最低的minSdkVersion 为 21的时候,如果想把应用安装在低版本的手机上就会出现如下问题:
10、在开发时 minSdkVersion 也起到一个重要角色:lint 默认会在项目中运行,它在你使用了高于 minSdkVersion 的 API 时会警告你,帮你避免调用不存在的 API 的运行时问题。如果只在较高版本的系统上才使用某些 API,通常使用运行时检查系统版本的方式解决。
11、请记住,你所使用的库,如 Support Library,可能有他们自己的 minSdkVersion 。你的应用设置的 minSdkVersion 必需大于等于这些库的 minSdkVersion 。例如有三个库,它们的 minSdkVersion 分别是 4, 7 和 9 ,那么你的 minSdkVersion 必需至少是 9 才能使用它们。在少数情况下,你仍然想用一个比你应用的 minSdkVersion 还高的库(处理所有的边缘情况,确保它只在较新的平台上使用),你可以使用 tools:overrideLibrary 标记,但请做彻底的测试。
12、minSdkVersion <= targetSdkVersion <= compileSdkVersion
13、我们在看一个Android版本和API Level的对应关系
ok,说了这么多我们现在就知道该怎么修改这几个值了,结论如下:
1、compileSdkVersion怎么确定呢?因为我想适配Android 7.0,compileSdkVersion也推荐使用最新的,因此我们把这个值设置为25。
2、buildeToolVersion怎么确定呢?由于buildeToolVersion要和compileSdkVersion保持一致,因此buildeToolVersion的值至少为25.0.0,可以根据Android Studio的提示设置更高的buildeToolVersion版本。
3、targetSdkVersion怎么确定呢?根据Android版本和API Level的对应关系图我们可以看到Android 7.0和7.1.1分别对应着24和25,两个差别应该不是太大,但是既然要做适配,我们就做的彻底点,那就设置为25吧。
4、minSdkVersion 怎么设置呢?为了不让我们适配不丢失现有的部分用户,也为了安全起见,我们决定还是保持现在项目中的minSdkVersion 不变。
5、根据知识点当中的第7条,我们Support Library版本要和compileSdkVersion保持一致,也升级到25.
这样经过一些列修改之后,我们这边的这几个版本数值如下:
compileSdkVersion 25
buildToolsVersion '25.0.3'
minSdkVersion 16
targetSdkVersion 25
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
在适配的过程中,25.0.3和25.3.1都是根据提示更新的,其中targetSdkVersion 25提示我不是最新的(
提示为:Not targeting the latest versions of Android; compatibility modes apply.
Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details.)
需要升级到26,但是我们适配到25就足够了,26毕竟现在还没普遍应用。
我们做安卓版本适配的第一步就完成了。
二、如何在代码当中添加权限检查和申请功能
我们先来看一下最终实现效果图:
点击禁止后进入到如下画面
点击设置后进入到权限设置画面
申请所需要的权限
那这个是怎么实现的呢?我们直接看一段代码,比如我在进入应用的时候要申请存储权限和打电话权限是怎么做的:
//执行某个操作前要进行检查和申请的权限
String[] permissionsArray = new String[]{DangerousPermissionConstants.WRITE_EXTERNAL_STORAGE, DangerousPermissionConstants.CALL_PHONE};
//检查权限
if (PermissionsUtil.hasPermission(StartActivity.this, permissionsArray)) {//如果权限都申请了则直接执行相应操作
initAfterCheckPermission();
}else {//否则执行权限申请
PermissionsUtil.requestPermission(StartActivity.this, new PermissionListener() {
@Override
public void permissionGranted(@NonNull String[] permission) {
initAfterCheckPermission();
}
@Override
public void permissionDenied(@NonNull String[] permission) {
finish();
}
}, PermissionsUtil.getUnGrantedPermissions(StartActivity.this,permissionsArray));
}
具体的工具类的实现代码如下,工具类是自己参考网上的代码并加入了自己的方法:
DangerousPermissionConstants .java
package com.yqzbw.yqms.lb.util.permission;
/**
* Created by 王笔锋 on 2017/8/3.
* Description: 该类定义了Android 6.0以后定义的危险权限
*/
public class DangerousPermissionConstants {
//联系人相关危险权限
//group:android.permission-group.CONTACTS
public static final String WRITE_CONTACTS = "android.permission.WRITE_CONTACTS";
public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
//电话相关权限
//group:android.permission-group.PHONE
public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
public static final String CALL_PHONE = "android.permission.CALL_PHONE";
public static final String WRITE_CALL_LOG = "android.permission.WRITE_CALL_LOG";
public static final String USE_SIP = "android.permission.USE_SIP";
public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
//日期相关权限
//group:android.permission-group.CALENDAR
public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
public static final String WRITE_CALENDAR = "android.permission.WRITE_CALENDAR";
//相机相关权限
//group:android.permission-group.CAMERA
public static final String CAMERA = "android.permission.CAMERA";
//感应器相关权限
//group:android.permission-group.SENSORS
public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
//地理位置相关权限
//group:android.permission-group.LOCATION
public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
//存储相关权限
//group:android.permission-group.STORAGE
public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
public static final String WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE";
//音频录制相关权限
//group:android.permission-group.MICROPHONE
public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
//发送接收短信相关权限
//group:android.permission-group.SMS
public static final String READ_SMS = "android.permission.READ_SMS";
public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
public static final String RECEIVE_MMS = "android.permission.RECEIVE_MMS";
public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
public static final String SEND_SMS = "android.permission.SEND_SMS";
public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
}
PermissionsUtil .java
package com.yqzbw.yqms.lb.util.permission;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Created by 王笔锋 on 2017/8/3.
* Description: 权限检查工具类
*/
public class PermissionsUtil {
public static final String TAG = PermissionsUtil.class.getSimpleName();
private static HashMap<String, PermissionListener> listenerMap = new HashMap();
/**
* 申请授权,当用户拒绝时,会显示默认一个默认的Dialog提示用户
* @param activity
* @param listener
* @param permission 要申请的权限
*/
public static void requestPermission(Activity activity, PermissionListener listener, String... permission) {
requestPermission(activity, listener, permission, true, null);
}
/**
* 申请授权,当用户拒绝时,可以设置是否显示Dialog提示用户,也可以设置提示用户的文本内容
* @param activity
* @param listener
* @param permission 需要申请授权的权限
* @param showTip 当用户拒绝授权时,是否显示提示
* @param tip 当用户拒绝时要显示Dialog设置
*/
public static void requestPermission(@NonNull Activity activity, @NonNull PermissionListener listener
, @NonNull String[] permission, boolean showTip, @Nullable TipInfo tip) {
if (listener == null) {
Log.e(TAG, "listener is null");
return;
}
/**
* 该部分只有当系统是6.0以下的才会执行
*/
if (Build.VERSION.SDK_INT < 23) {
if (PermissionsUtil.hasPermission(activity, permission)) {
listener.permissionGranted(permission);
} else {
listener.permissionDenied(permission);
}
Log.e(TAG, "API level : " + Build.VERSION.SDK_INT + "不需要申请动态权限!");
return;
}
String key = String.valueOf(System.currentTimeMillis());
//这个地方很关键,将listener传递到Activity当中进行处理
listenerMap.put(key, listener);
Intent intent = new Intent(activity, PermissionActivity.class);
intent.putExtra("permission", permission);
intent.putExtra("key", key);
intent.putExtra("showTip", showTip);
intent.putExtra("tip", tip);
activity.startActivity(intent);
}
/**
* 判断权限是否授权:只要有一个没有授权就返回false
* @param context
* @param permissions
* @return
*/
public static boolean hasPermission(@NonNull Context context, @NonNull String... permissions) {
if (permissions.length == 0) {
return false;
}
for (String per : permissions ) {
int result = PermissionChecker.checkSelfPermission(context, per);
if ( result != PermissionChecker.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 返回未被授权的权限数组
* @param permissions 所有待检查是否授权的权限
* @return
*/
public static String[] getUnGrantedPermissions(Activity context,String... permissions) {
String[] unGrantedPermissions;
List<String> permissionList = new ArrayList<>();
if (permissions.length == 0) {
return null;
}
//遍历权限数组,查找未被授权的权限
for (String permission : permissions ) {
int result = PermissionChecker.checkSelfPermission(context, permission);
if ( result != PermissionChecker.PERMISSION_GRANTED) {
permissionList.add(permission);
}
}
//遍历List,给未赋值的权限列表赋值
unGrantedPermissions = new String[permissionList.size()];
for (int i = 0; i < permissionList.size(); i++) {
unGrantedPermissions[i] = permissionList.get(i);
}
return unGrantedPermissions;
}
/**
* 判断一组授权结果是否为授权通过
* @param grantResult
* @return
*/
public static boolean isGranted(@NonNull int... grantResult) {
if (grantResult.length == 0) {
return false;
}
for (int result : grantResult) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 跳转到当前应用对应的设置页面
* @param context
*/
public static void gotoSetting(@NonNull Context context) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + context.getPackageName()));
context.startActivity(intent);
}
/**
*
* @param key
* @return
*/
static PermissionListener fetchListener(String key) {
return listenerMap.remove(key);
}
public static class TipInfo implements Serializable {
private static final long serialVersionUID = 1L;
String title;
String content;
String cancel; //取消按钮文本
String ensure; //确定按钮文本
public TipInfo ( @Nullable String title, @Nullable String content, @Nullable String cancel, @Nullable String ensure) {
this.title = title;
this.content = content;
this.cancel = cancel;
this.ensure = ensure;
}
}
}
PermissionActivity.java
package com.yqzbw.yqms.lb.util.permission;
/**
* Created by 王笔锋 on 2017/8/3.
* Description: 该类定义了要申请的权限被用户拒绝了后提示用户打开权限的界面
*/
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import java.io.Serializable;
public class PermissionActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 64;
private boolean isRequireCheck;
private String[] permission;
private String key;
private boolean showTip;
private PermissionsUtil.TipInfo tipInfo;
private final String defaultTitle = "帮助";
private final String defaultContent = "当前应用缺少必要权限。\n \n 请点击 \"设置\"-\"权限\"-打开所需权限。";
private final String defaultCancel = "取消";
private final String defaultEnsure = "设置";
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent() == null || !getIntent().hasExtra("permission")) {
finish();
return;
}
isRequireCheck = true;
permission = getIntent().getStringArrayExtra("permission");
//在这里根据传进来的permission动态赋值
key = getIntent().getStringExtra("key");
showTip = getIntent().getBooleanExtra("showTip", true);
Serializable ser = getIntent().getSerializableExtra("tip");
if (ser == null) {
tipInfo = new PermissionsUtil.TipInfo(defaultTitle, defaultContent, defaultCancel, defaultEnsure);
} else {
tipInfo = (PermissionsUtil.TipInfo)ser;
}
}
@Override protected void onResume() {// 请求权限,回调时会触发onResume
super.onResume();
if (isRequireCheck) {
//判断授权是否都通过了
if (PermissionsUtil.hasPermission(this, permission)) {//如果授权都通过了
permissionsGranted();
} else {//如果还有授权未通过
// requestPermissions(permission);
//改地方做了修改,申请权限的时只申请为授权过的权限
requestPermissions(PermissionsUtil.getUnGrantedPermissions(PermissionActivity.this,permission));
isRequireCheck = false;
}
} else {
isRequireCheck = true;
}
}
// 请求权限兼容低版本
private void requestPermissions(String[] permission) {
ActivityCompat.requestPermissions(this, permission, PERMISSION_REQUEST_CODE);
}
/**
* 用户权限处理,
* 如果全部获取, 则直接过.
* 如果权限缺失, 则提示Dialog.
*
* @param requestCode 请求码
* @param permissions 权限
* @param grantResults 结果
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//部分厂商手机系统返回授权成功时,厂商可以拒绝权限,所以要用PermissionChecker二次判断
if (requestCode == PERMISSION_REQUEST_CODE && PermissionsUtil.isGranted(grantResults)
&& PermissionsUtil.hasPermission(this, permissions)) {
permissionsGranted();
} else if (showTip){
for (int i = 0 ; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
String permissionName = getPermissionName(permissions[i]);
tipInfo.content = "当前应用缺少" + permissionName + "权限。\n \n 请点击 \"设置\"-\"权限\"-打开所需权限。";
break;
}
}
showMissingPermissionDialog();
} else { //不需要提示用户
permissionsDenied();
}
}
/**
* 根据危险权限的字符串值获取它的中文名称
* @param permission 危险权限的字符串值
* @return 权限中文名称
*/
private String getPermissionName(String permission) {
String permissionName = "";
switch (permission) {
case DangerousPermissionConstants.CAMERA:
permissionName = "相机";
break;
case DangerousPermissionConstants.WRITE_EXTERNAL_STORAGE:
permissionName = "存储";
break;
case DangerousPermissionConstants.READ_EXTERNAL_STORAGE:
permissionName = "存储";
break;
default:
break;
}
return permissionName;
}
// 显示缺失权限提示
private void showMissingPermissionDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(PermissionActivity.this);
builder.setTitle(TextUtils.isEmpty(tipInfo.title) ? defaultTitle : tipInfo.title);
builder.setMessage(TextUtils.isEmpty(tipInfo.content) ? defaultContent : tipInfo.content);
builder.setNegativeButton(TextUtils.isEmpty(tipInfo.cancel) ? defaultCancel : tipInfo.cancel, new DialogInterface.OnClickListener(){
@Override public void onClick(DialogInterface dialog, int which) {
permissionsDenied();
}
});
builder.setPositiveButton(TextUtils.isEmpty(tipInfo.ensure) ? defaultEnsure : tipInfo.ensure, new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
PermissionsUtil.gotoSetting(PermissionActivity.this);
}
});
builder.setCancelable(false);
builder.show();
}
private void permissionsDenied() {
PermissionListener listener = PermissionsUtil.fetchListener(key);
if (listener != null) {
listener.permissionDenied(permission);
}
finish();
}
// 全部权限均已获取
private void permissionsGranted() {
PermissionListener listener = PermissionsUtil.fetchListener(key);
if (listener != null) {
listener.permissionGranted(permission);
}
finish();
}
protected void onDestroy() {
PermissionsUtil.fetchListener(key);
super.onDestroy();
}
}
PermissionListener .java
package com.yqzbw.yqms.lb.util.permission;
import android.support.annotation.NonNull;
/**
* Created by 王笔锋 on 2017/8/3.
* Description: 该类定义了授权通过或者失败要执行的方法
*/
public interface PermissionListener {
/**
* 通过授权
* @param permission
*/
void permissionGranted(@NonNull String[] permission);
/**
* 拒绝授权
* @param permission
*/
void permissionDenied(@NonNull String[] permission);
}
三、如何解决帮助页面背景空白的问题
如果你也是按照我这边的代码进行尝试就会发现一个小问题,如下图:
发现没有,背景变成空白了,原因是从当前Activity跳转到PermissionActivity的时候被覆盖了的缘故,这样给用户带来的体验就不是很好,解决办法就是将PermissionActivity设置为Dialog样式,并在AndroidManifest.xml文件当中引用该样式即可,关键代码如下:
<!--权限检查Dialog样式-->
<style name="PermissionCheckActivityDialogStyle" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowCloseOnTouchOutside">false</item>
<item name="android:windowIsFloating">true</item>
</style>
<!--权限检查Activity样式注册-->
<activity
android:name="com.yqzbw.yqms.lb.util.permission.PermissionActivity"
android:screenOrientation="portrait"
android:theme="@style/PermissionCheckActivityDialogStyle" />
这样就可以看到背景内容啦,最终效果图如下(图中敏感信息已用方块过滤):
四、获取位置权限适配
由于位置权限不能强制用户打开,因此这个地方我就没有实用工具类来做,而是直接用的原始代码,具体如下:
权限申请
if (ActivityCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(LoginActivity.this, new String[]{DangerousPermissionConstants.ACCESS_FINE_LOCATION}, 1234);
}
权限结果返回
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1234:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//给经纬度信息赋值
getPositionInfo();
}
//进行用户登录操作
doLogin();
break;
default:
break;
}
}
/**
* 获取首次登陆用户的经纬度信息
*/
private void getPositionInfo() {
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//获取所有可用的位置提供器
List<String> providers = locationManager.getProviders(true);
String locationProvider;
if (providers.contains(LocationManager.GPS_PROVIDER)) {
//如果是GPS
locationProvider = LocationManager.GPS_PROVIDER;
} else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
//如果是Network
locationProvider = LocationManager.NETWORK_PROVIDER;
} else {
locationProvider = null;
}
//获取Location
if (locationProvider != null) {
try {
location = locationManager.getLastKnownLocation(locationProvider);
if (location != null) {
String locationStr = "维度:" + location.getLatitude() + "\n" + "经度:" + location.getLongitude();
latitude = location.getLatitude() + "";
longitude = location.getLongitude() + "";
Log.e(TAG, "*******经纬度信息 " + locationStr );
}
} catch (SecurityException e) {
e.printStackTrace();
}
}
}
五、相机拍照图片保存适配
在Android7.0之前,如果你想调用系统相机拍照可以通过以下代码来进行:
Intent takeIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//下面这句指定调用相机拍照后的照片存储的路径
takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mTempPhotoPath)));
startActivityForResult(takeIntent, requestCode);
在Android7.0上使用上述方式调用系统相拍照会抛出如下异常:
android.os.FileUriExposedException: file:////storage/emulated/0/temp/1474956193735.jpg exposed beyond app through Intent.getData()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8933)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
at android.app.Activity.startActivityForResult(Activity.java:4223)
...
这是由于Android7.0执行了“StrictMode API 政策禁”的原因,这个需要用FileProvider来解决这一问题,具体步骤如下:
第一步:在manifest清单文件中注册provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.yqms.takephoto.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
心得:exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限。
第二步:指定共享的目录
为了指定共享的目录我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="camera_photos"
path="" />
</paths>
</resources>
- <files-path/>代表的根目录: Context.getFilesDir()
- <external-path/>代表的根目录: Environment.getExternalStorageDirectory()
- <cache-path/>代表的根目录: getCacheDir()
心得:上述代码中path="",是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了,如果你将path设为path="pictures",那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
第三步:使用FileProvider
Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
//intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//下面这句指定调用相机拍照后的照片存储的路径
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(mContext,"com.yqms.takephoto.fileprovider",new File(mTempPhotoPath)));
startActivityForResult(takeIntent, 40);
当然目前还可能有Android 7.0其他需要适配的地方,会进一步完善该文章。
如果文章当中有任何不正确的地方,还请广大读者纠正,非常感谢!
网友评论