传送门:安卓开发中的系统版本兼容的注意事项(一)(Android5.0 API21)
安卓开发中的系统版本兼容的注意事项(二)(Android6.0 API23)
安卓开发中的系统版本兼容的注意事项(三)(Android7.0 API24)
作为安卓开发者最头疼的一点,莫过于谷歌越来越快的版本更新速度。以及升级编译版本后需要面对的大量兼容性异常。尤其是今年电信终端产业协会(TAF)发布了《移动应用软件高API等级预置与分发自律公约》。https://baike.baidu.com/item/移动应用软件高API等级预置与分发自律公约/22759862
逼着你升级,想不升级都不行。
下面将根据自己实际项目中升级开发版本的经验,对每个版本的注意事项做一下总结性回顾。方便自己以后查阅方便,也可以给有这方面需求的新手提供一点借鉴。
升级到8.0+以后,我们会发现,在使用registerContentObserver和notifyChange方法时。APP直接异常崩溃了, 后台异常提示为:SecurityException
这是因为Android 8.0 引入了新的广播接收器限制,应用中所有为隐式广播 Intent 注册的广播接收器都不能正常使用了。官方建议使用动态广播代替静态广播。具体的操作方法如下:
1.首先在AndroidManifest.xml
中声明ContentProvider
这里的MyContentProvider是我们统一注册的广播注册和发送工具,"com.shengcai.observer.MyContentProvider"是授权参数,后面会用到,可自行修改。
2.创建MyContentProvider类,继承自ContentProvider。在这里我们统一对所有要用到了APP广播消息进行注册,并对通知的URI进行统一管理。源码如下:
public class MyContentProvider extends ContentProvider {
private static final String SCHEME = "content";
private static final String AUTHORITY = "com.shengcai.observer.MyContentProvider";
private static final UriMatcher matcher;
public static final String newUserLogin = "sc_new_user_login";// 登录环信服务器成功,通知重新注册消息接收器
//APP内所有的广播,可自行扩展
...
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY,newUserLogin,0);
//初始化所有的广播,可自行扩展
...
}
public static Uri getUri(String path){
return new Uri.Builder().authority(AUTHORITY).path(path).scheme(SCHEME).build();
}
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType( Uri uri) {
return null;
}
@Override
public Uri insert( Uri uri, ContentValues values) {
return null;
}
@Override
public int delete( Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
3.动态使用registerContentObserver和notifyChange方法,代码如下:
getContentResolver().registerContentObserver(MyContentProvider.getUri(MyContentProvider.newUserLogin), true, mNewLoginObserver);//注册广播接受
getContentResolver().notifyChange(MyContentProvider.getUri(MyContentProvider.newUserLogin), null);//发送广播通知
这样,我们使用时直接通过这个工具类进行注册和发送消息通知就可以啦,是不是很方便呢。
检查并更新的功能,相信绝大部分APP都有。最简单的实现方法,使用application/vnd.android.package-archive,打开系统的安装界面即可,但是8.0以上又出现问题了,调用完后直接崩溃卡死,后台提示没有安装未知应用的权限。
这是Android8.0的诸多新特性中一个非常重要的特性:未知来源应用权限
之前版本安装未知来源应用的时候一般会弹出一个弹窗让用户去设置允许还是拒绝,并且设置为允许之后,所有的未知来源的应用都可以被安装。
Android8.0的变化是,未知应用安装权限的开关被除掉,取而代之的是未知来源应用的管理列表,需要在里面打开每个应用的未知来源的安装权限。Google这么做是为了防止一开始正经的应用后来开始通过升级来做一些不合法的事情,侵犯用户权益。
当你的应用直接适配到8.0之后,内部启动应用安装是默认被拒绝的,如果不处理好这个未知来源的权限,会导致应用根本无法更新,只能去应用市场重新下载。
解决办法如下:
1.首先在AndroidManifest.xml
中声明安装权限,
2.在调用安装方法之前先进行如下判断
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !mContext.getPackageManager().canRequestPackageInstalls() )
谷歌提供了canRequestPackageInstalls方法来判断当前APP是否具有安装未知来源应用的权限,注意必须是8.0以上的才需要使用这个方法,否则,会一直返回false。
如果有权限,可以继续执行安装,如果没有权限,则需要提示用户进入设置界面,开启权限后才能安装。打开安装未知来源应用的权限设置界面代码如下:
Uri packageURI = Uri.parse("package:" + mContext.getPackageName()); Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI); mContext.startActivityForResult(intent, Request_Result_Code.INSTALL_PACKAGE);
我们可以在onActivityResult中重新使用canRequestPackageInstalls方法判断用户是否完成了授权。
最后需要注意的一点,应用内更新时还需要考虑到7.0系统关于FileProvider的兼容性,(参考上一篇)所以完整的兼容处理代码如下所示:
/*安装APP软件更新的公共方法-需要重写onActivityResult,在用户完成授权后,执行回调*/ public static void installNewPackage(final Activity mContext) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !mContext.getPackageManager().canRequestPackageInstalls() ) { //没有安装权限,需要去设置里面重新打开才能使用 DialogUtil.showAlertWithCallback(mContext, "权限获取失败", "没有安装不能更新APP,是否进入应用设置中进行权限设置?", "好的", "取消", new ClickCallback() { @Override public void noClick() { } @Override public void yesClick() { //打开权限设置界面 try { Uri packageURI = Uri.parse("package:" + mContext.getPackageName()); Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI); mContext.startActivityForResult(intent, Request_Result_Code.INSTALL_PACKAGE); } catch (Exception e) { e.printStackTrace(); } } }); return; } //7.0以上通过FileProvider,安装更新 try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Uri uri = FileProvider.getUriForFile(mContext, "com.shengcai.fileprovider", yourNewAppFile); Intent intent = new Intent(Intent.ACTION_VIEW).setDataAndType(uri, "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); mContext.startActivity(intent); } else { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(yourNewAppFile), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } } catch (Exception e) { e.printStackTrace(); } }