●安装apk的权限
首先第一步:我们需要在清单文件中添加一项权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
有了这个权限之后,安装apk时判断:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !getPackageManager().canRequestPackageInstalls()){ PermissionUtil.requestPermissions(this,PermissionUtil.PERMISSION_INSTALL_PACKAGES_CODE, Manifest.permission.REQUEST_INSTALL_PACKAGES); }
具体权限管理请移步文章开头的6.0的权限管理文章查看。
●通知栏的适配
Android O版本对通知做了规范性的控制,强制用户在发送通知的时候,对通知进行系统性的管理,新增了channel渠道功能:
private String Notification_ID = "5"; private String Notification_CHANNEL_1 = "channel5";
private void showNotification(long cancelTime, String text) { try { Context app = context.getApplicationContext(); NotificationManager nm = (NotificationManager) app .getSystemService(Context.NOTIFICATION_SERVICE); final int id = Integer.MAX_VALUE / 13 + 1; nm.cancel(id); long when = System.currentTimeMillis(); Notification notification; PendingIntent pi = PendingIntent.getActivity(app, 0, new Intent(), 0); if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.O){ NotificationChannel channel = new NotificationChannel(String.valueOf(Notification_ID), Notification_CHANNEL_1, NotificationManager.IMPORTANCE_LOW); channel.enableLights(true);//是否在桌面icon右上角展示小红点 channel.setLightColor(Color.GREEN);//小红点颜色 channel.setShowBadge(true); //是否在久按桌面图标时显示此渠道的通知 nm.createNotificationChannel(channel); notification = new Notification.Builder(context) .setChannelId(String.valueOf(Notification_ID))//该句适配android 8.0 版本 .setSmallIcon(notifyIcon) .setContentTitle(notifyTitle) .setTicker(text) .setContentText(text) .setWhen(when) .setContentIntent(pi).setNumber(0) .getNotification(); notification.flags |= Notification.FLAG_AUTO_CANCEL; }else { notification = new Notification.Builder(context) .setSmallIcon(notifyIcon) .setContentTitle(notifyTitle) .setTicker(text) .setContentText(text) .setContentIntent(pi).setNumber(0) .setWhen(when) .getNotification(); notification.flags |= Notification.FLAG_AUTO_CANCEL; } nm.notify(id, notification); if (cancelTime > 0) { Message msg = new Message(); msg.what = MSG_CANCEL_NOTIFY; msg.obj = nm; msg.arg1 = id; UIHandler.sendMessageDelayed(msg, cancelTime, this); } } catch (Exception e) { e.printStackTrace(); } }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//关闭通知通道
notificationManager.deleteNotificationChannel("1");
}
在Android O之前的系统中申请了该权限后,再给对应的window设置type:WindowManager.LayoutParams.TYPE_SYSTEM_ALERT悬浮窗口就可以显示出来。但是在Android O的系统中,google规定申请"android.permission.SYSTEM_ALERT_WINDOW"权限的应用需要给悬浮窗设置type:WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY.
悬浮窗口才能显示出来,如果不设置该TYPE,会Crash,8.0代码修改如下:
public static void showLogoutDialog(final Context context) { AlertDialog.Builder builder = new AlertDialog.Builder(context); View view = View.inflate(context, R.layout.dialog_logout, null); builder.setView(view); final AlertDialog dialog = builder.create(); //设置弹出全局对话框,但是这句话并不能解决在android的其他手机上能弹出来(例如用户华为p10 就无法弹框) // dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST); //只有这样才能弹框 if (Build.VERSION.SDK_INT>=26) {//8.0新特性 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); }else{ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); } dialog.show(); }另外,6.0后需要申请悬浮窗权限,属于特殊权限,关于此权限的申请请看文章开头的6.0的适配文章链接;
Android 8.0透明Activity报错 "Only fullscreen activities can request orientation"
1、分析问题
首先,我的代码是这样的:
style.xml
<style name="app_transparent_activity" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowContentTransitions">true</item>
<item name="android:windowSoftInputMode">adjustPan</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowBackground">@color/app_transparent_color</item>
<item name="android:windowIsTranslucent">true</item>//透明
<item name="android:windowIsFloating">true</item>//悬浮
</style>
- AndroidManifest.xml
<activity
android:name=".appmanager.mianframe.UsagePermissionGuideActivity"
android:launchMode="singleInstance"
android:screenOrientation="portrait"
android:theme="@style/app_transparent_activity"/>
从上面可以看出,我的activity是一个状态栏透明并且背景透明的activity。在26的编译版本时是可以正常的使用的,但是当把编译版本升级到27时,就会出现”Only fullscreen activities can request orientation”异常。这是google出于安全的考虑,对android8.0以后的版本做的处理,当一个Activity固定方向并且是透明的,在8.0以后的版本中就会抛出异常。相关源码如下:
Entry ent = AttributeCache.instance().get(packageName,realTheme, com.android.internal.R.styleable.Window, userId);
final boolean translucent = ent != null && (ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent, false)|| (!ent.array.hasValue(
com.android.internal.R.styleable.Window_windowIsTranslucent) && ent.array.getBoolean(com.android.internal.R.styleable.Window_windowSwipeToDismiss,false)));
fullscreen = ent != null && !ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false) && !translucent;
fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
noDisplay = ent != null && ent.array.getBoolean(com.android.internal.R.styleable.Window_windowNoDisplay, false);
if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen && appInfo.targetSdkVersion >= O) {
throw new IllegalStateException("Only fullscreen activities can request orientation");
}
上面是27的源码片段,通过上面我们可以看出当三个条件同时满足的时候,系统会抛出”Only fullscreen activities can request orientation”异常。先分别来说说这三个条件都表示什么意思:
- ActivityInfo.isFixedOrientation(requestedOrientation) —— 表示判断当前的|Activity是否固定了方向
- fullscreen —— 表示Activity是否是透明的或者是否悬浮在Activity上,是透明的或者悬浮在Activity上fullscreen就等于false
- appInfo.targetSdkVersion >= O —— 表示版本号大于等于26
当以上的三个条件同时满足的时候,系统框架就会抛出异常。
2、解决问题的方法
这个问题在最新的SDK中已经修复,我们在API Level 27的设备上已经无法重现,但我们手头的API Level 26的设备还是能重现。而且根据上面的代码来看,如果想保留当前Activity的style,“isTranslucentOrFloating”的逻辑根本没法绕过,所以想绕开很难,目前能想到的大概两个方向:
- 1.推迟SDK升级,等官方修复被大多数设备采用;
- 2.升级SDK,但重构一下代码,看看已有的非“fullscreen” Activity是不是都是必要的,例如用Fragment实现周围半透明效果,能不能直接把Fragment加入到当前Activity(当然Detach Fragment是有重绘View的开销的)。
- 3.使用Dialog代替透明的Activity
- 4.不固定Activity的方向
- 5.使用不透明且不是悬浮的主题(我采用此解决办法,8.0以后改为不透明不悬浮)