一. Android6.0(M)
1.1. 权限
Android系统6.x 的权限分为危险权限(不涉及危险性信息泄露)和普通权限(涉及用户私人信息),危险权限需要动态添加授权申请,不仅仅在清单文件中添加申请。危险权限是分组(9组)的,当组内的一个权限被授予可以执行则其他权限皆可执行。
1.1.1. 列举权限的分组
<!-- 危险权限 start -->
<!--PHONE-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!--CALENDAR-->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!--CAMERA-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--CONTACTS-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!--LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--MICROPHONE-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--SENSORS-->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!--SMS-->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!--STORAGE-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 危险权限 Permissions end -->
1.1.2. 示例
1.1.2.1. 清单文件(AndroidManifest)注册
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
1.1.2.2. 逻辑代码
public class PermActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_perm);
getPersimmionInfo();
}
//**************授权信息
private void getPersimmionInfo() {
if (Build.VERSION.SDK_INT >= 23) {
//1. 检测是否添加权限 PERMISSION_GRANTED 表示已经授权并可以使用
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) ||
(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) {
//手机为Android6.0的版本,权限未授权去i请授权
//2. 申请请求授权权限
//1. Activity
// 2. 申请的权限名称
// 3. 申请权限的 请求码
ActivityCompat.requestPermissions(this, new String[]
{Manifest.permission.READ_EXTERNAL_STORAGE,//读写Sd卡
Manifest.permission.CAMERA//照相机
},//读写内存卡
666);//
} else {//手机为Android6.0的版本,权限已授权可以使用
Toast.makeText(this, "手机为Android6.0的版本,权限已授权可以使用", Toast.LENGTH_SHORT).show();
}
} else {//手机为Android6.0以前的版本,可以使用
Toast.makeText(this, "手机为Android6.0以前的版本可以使用", Toast.LENGTH_SHORT).show();
}
}
// 重写方法 当权限申请后 执行 接收结果的作用
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 先判断请求码 与 请求时的 是否一致
if (requestCode == 666) {
// 判断请求结果长度 且 结果 为 允许访问 则 进行使用;第一次授权成功后
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
// 可以使用
Toast.makeText(this, "已经授权读写SD卡和照相机权限,可以使用", Toast.LENGTH_SHORT).show();
} else {//未授权不可以使用
// 读写内存卡权限授权将不能使用以下功能。
Toast.makeText(this, "未授权不可以使用", Toast.LENGTH_SHORT).show();
}
}
}
}
1.2. getResources.getColor()方法遭弃用
我们在开发过程中,偶尔会遇到部分api会被划一下,也就是当前使用的API过时了,那么官方肯定给出的有其替代API。现在要说的就是在android 23(6.0)及以上
getResources().getColor(R.color.black)
API过时时,那么它的替代方法为
ContextCompat.getColor(this,R.color.blue)
可以使用最新的V4兼容包中的ContextCompat,这样也可以兼容低版本的平台。
二. Android7.0(N)
(1)APP应用程序的私有文件不再向使用者放宽
(2)Intent组件传递file://URI的方式可能给接收器留下无法访问的路径,触发FileUriExposedException异常,推荐使用FileProvider
(3)DownloadManager不再按文件名分享私人存储的文件。旧版应用在访问COLUMN_LOCAL_FILENAME时
2.1. FileProvider
2.1.1.问题
Caused by: android.os.FileUriExposedException:
file:///storage/emulated/0/20170601-030254.png
exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1932)
at android.net.Uri.checkFileUriExposed(Uri.java:2348)
2.1.2.原因
在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException。
2.1.3.解决方案
FileProvider实际上是ContentProvider的一个子类,它的作用也比较明显了,file:///Uri不给用,那么换个Uri为content://来替代
(1)在资源文件新建xml目录,新建文件
main/res/xml/ rc_file_path
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
</paths>
(2)清单文件配置
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="com.fsh.lfmf.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/rc_file_path" />
</provider>
```
(3)
在paths节点内部支持以下几个子节点,分别为:
<root-path/> 代表设备的根目录new File("/");
<files-path/> 代表context.getFilesDir()
<cache-path/> 代表context.getCacheDir()
<external-path/> 代表Environment.getExternalStorageDirectory()
<external-files-path>代表context.getExternalFilesDirs()
<external-cache-path>代表getExternalCacheDirs()
每个节点都支持两个属性:
name
path
path即为代表目录下的子目录,比如:
<external-path
name="external"
path="pics" />
代表的目录即为:Environment.getExternalStorageDirectory()/pics,其他同理。
当这么声明以后,代码可以使用你所声明的当前文件夹以及其子文件夹。
刚才我们说了,现在要使用content://uri替代file://uri,那么,content://的uri如何定义呢?总不能使用文件路径吧,那不是骗自己么~
所以,需要一个虚拟的路径对文件路径进行映射,所以需要编写个xml文件,通过path以及xml节点确定可访问的目录,通过name属性来映射真实的文件路径。
/**
* 拍照
*/
public void camera(int photoType) {
// 判断存储卡是否可以用,可用进行存储
if (hasSdcard()) {
File dir = new File(IMAGE_DIR);
if (!dir.exists()) {
dir.mkdir();
}
tempFile = new File(dir,
System.currentTimeMillis() + "_" + PHOTO_FILE_NAME);
//从文件中创建uri
Uri uri = null;
//判断是否是Android7.0以及更高的版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".FileProvider", tempFile);
} else {
uri = Uri.fromFile(tempFile);
}
Intent intent = new Intent();
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addCategory(intent.CATEGORY_DEFAULT);
// 开启一个带有返回值的Activity,请求码为PHOTO_P_CAREMA
startActivityForResult(intent, photoType);
} else {
ToastUtil.showShort(this, "未找到存储卡,无法拍照!");
}
}
### 2.1.4.用途场景
(1)拍照
(2)版本更新,安置app。
## 2.2. 使用FileProvider兼容安装apk
### 2.2.1.Android7.0以前安装apk
```java
public void installApk(View view) {
File file = new File(Environment.getExternalStorageDirectory(), "testandroid7-debug.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
startActivity(intent);
}
拿个7.0的原生手机跑一下,android.os.FileUriExposedException又来了~~
android.os.FileUriExposedException: file:///storage/emulated/0/testandroid7-debug.apk exposed beyond app through Intent.getData()
2.2.2.Android7.0以后安装apk
protected void installApk(File file) {
Intent intent = new Intent();
// 执行动作
intent.setAction(Intent.ACTION_VIEW);
//从文件中创建uri
Uri uri = null;
//判断是否是Android7.0以及更高的版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//7.0
uri = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".FileProvider", file);
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//添加这一句表示对目标应用临时授权该Uri所代表的文件
install.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(install);
} else {
uri = Uri.fromFile(file);
// 执行的数据类型
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(intent);
}
}
三. Android8.0(O)
3.1. 通知适配
public void notification() {
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//判断是否是8.0Android.O
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel chan1 = new NotificationChannel(NotificationConstant.PUSH_CHANNEL_ID,
NotificationConstant.PUSH_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
chan1.setLightColor(Color.GREEN);
chan1.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
notificationManager.createNotificationChannel(chan1);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationConstant.PUSH_CHANNEL_ID);
// Intent notificationIntent = new Intent(this, SecActivity.class);
// notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Intent notificationIntent = new Intent(this, NotificationBroadcastReceiver.class);
notificationIntent.setAction("notification_clicked");
notificationIntent.putExtra(NotificationBroadcastReceiver.TYPE2, 111222);
notificationIntent.putExtra(NotificationBroadcastReceiver.TYPE, 11);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, 0);
RemoteViews rv = new RemoteViews(getPackageName(), R.layout.notification_layout);
//修改自定义View中的图片(两种方法)
//rv.setImageViewResource(R.id.iv,R.mipmap.ic_launcher);
rv.setImageViewBitmap(R.id.iv_icon, BitmapFactory.decodeResource(getResources(), R.drawable.add_round_green));
rv.setImageViewBitmap(R.id.iv_content_icon, BitmapFactory.decodeResource(getResources(), R.drawable.add_round_green));
rv.setTextViewText(R.id.tv_title, "泡沫");//修改自定义View中的歌名
rv.setTextViewText(R.id.tv_content, "内容");//修改自定义View中的歌名
builder.setContentTitle("通知标题")//设置通知栏标题
.setContentIntent(pendingIntent) //设置通知栏点击意图
.setNumber(++pushNum)
.setTicker("通知内容") //通知首次出现在通知栏,带上升动画效果的
.setWhen(System.currentTimeMillis())//通知产生的时间,会在通知信息里显示,一般是系统获取到的时间
.setSmallIcon(R.mipmap.ic_launcher)//设置通知小ICON
.setChannelId(NotificationConstant.PUSH_CHANNEL_ID)//通知渠道id
.setContent(rv)//自定义通知栏布局
.setAutoCancel(true)//可以点击通知栏的删除按钮删除
.setDefaults(Notification.DEFAULT_ALL);//通知默认的声音 震动 呼吸灯
Notification notification = builder.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
if (notificationManager != null) {
notificationManager.notify(NotificationConstant.PUSH_NOTIFICATION_ID, notification);
}
}
3.2.安装apk
Android 8.0去除了“允许未知来源”选项,所以如果我们的App有安装App的功能(检查更新之类的),那么会无法正常安装。
首先在AndroidManifest文件中添加安装未知来源应用的权限:
<!-- 如果是安卓8.0,应用编译配置的targetSdkVersion>=26,请务必添加以下权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
这样系统会自动询问用户完成授权。当然你也可以先使用 canRequestPackageInstalls()查询是否有此权限,如果没有的话使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES这个action将用户引导至安装未知应用权限界面去授权。
示例:
public class SecActivity extends AppCompatActivity {
private static final int REQUEST_CODE_UNKNOWN_APP = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sec);
}
private void installAPK(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean hasInstallPermission = getPackageManager().canRequestPackageInstalls();
if (hasInstallPermission) {
//安装应用
} else {
//跳转至“安装未知应用”权限界面,引导用户开启权限
Uri selfPackageUri = Uri.parse("package:" + this.getPackageName());
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, selfPackageUri);
startActivityForResult(intent, REQUEST_CODE_UNKNOWN_APP);
}
}else {
//安装应用
}
}
//接收“安装未知应用”权限的开启结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_UNKNOWN_APP) {
boolean hasInstallPermission = false;
if(android.os.Build.VERSION.SDK_INT>= android.os.Build.VERSION_CODES.O) {
hasInstallPermission= getPackageManager().canRequestPackageInstalls();
}
if (hasInstallPermission) {
//安装应用
installAPK();
}
}
}
}
3.3. Only fullscreen opaque activities can request orientation
3.3.1.问题
Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
at android.app.Activity.onCreate(Activity.java:1038)
at android.support.v4.app.SupportActivity.onCreate(SupportActivity.java:66)
at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:321)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84)
at com.fsh.lfmf.base.MyBaseActivity.onCreate(MyBaseActivity.java:24)
at com.fsh.lfmf.activity.RegisterActivity.onCreate(RegisterActivity.java:102)
at android.app.Activity.performCreate(Activity.java:7183)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
3.3.2.原因
在Android8.0环境下,如果一个Activity在Manifest中设置了方向(横,竖屏),即android:screenOrientation=”landscape”/”portrait”,并且指定的android:theme中使用的style带有透明属性
3.3.3.解决方法
方法一:Activity设置android:windowIsTranslucent=false,然后设置指定屏幕方向;
方法二:Activity设置android:windowIsTranslucent=true,然后设置android:screenOrientation=”behind”,这样就可以保持屏幕方向统一了。
四.Android9.0(P)
4.1.网络请求
4.1.1.问题
CLEARTEXT communication to life.115.com not permitted by network security policy。
4.1.2.原因
Android P 限制了明文流量的网络请求(http),非加密的流量请求都会被系统禁止掉。建议使用https进行传输。
4.1.3.解决方案
(1)在资源文件新建xml目录,新建文件
main/res/xml/ network_security_config
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
(2)清单文件配置
<application
android:networkSecurityConfig="@xml/network_security_config">
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
</application>
4.2. 其他Api的修改
4.2.1.问题
java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed。
4.1.2. 解决方案
if (Build.VERSION.SDK_INT >= 26) {
canvas.clipPath(mPath);
} else {
canvas.clipPath(mPath, Region.Op.REPLACE);
}