Android10填坑适配指南(实际经验代码)
今天看到一篇好的文章,分享给大家,膜拜大佬。
android10填坑适配指南,包含实际经验代码,绝不照搬翻译文档
1.region.op相关异常:java.lang.illegalargumentexception: invalid region.op - only intersect and difference are allowed
当 targetsdkversion >= build.version_codes.p 时调用 canvas.clippath(path, region.op.xxx); 引起的异常,参考源码如下:
@deprecated public boolean clippath(@nonnull path path, @nonnull region.op op) { checkvalidclipop(op); return nclippath(mnativecanvaswrapper, path.readonlyni(), op.nativeint); } private static void checkvalidclipop(@nonnull region.op op) { if (scompatiblityversion >= build.version_codes.p && op != region.op.intersect && op != region.op.difference) { throw new illegalargumentexception( "invalid region.op - only intersect and difference are allowed"); } }
我们可以看到当目标版本从android p开始,canvas.clippath(@nonnull path path, @nonnull region.op op) ; 已经被废弃,而且是包含异常风险的废弃api,只有 region.op.intersect 和 region.op.difference 得到兼容,几乎所有的博客解决方案都是如下简单粗暴:
if (build.version.sdk_int >= build.version_codes.p) { canvas.clippath(path); } else { canvas.clippath(path, region.op.xor);// replace、union 等 }
但我们一定需要一些高级逻辑运算效果怎么办?如小说的仿真翻页阅读效果,解决方案如下,用path.op代替,先运算path,再
给canvas.clippath: if(build.version.sdk_int >= build.version_codes.p){ path mpathxor = new path(); mpathxor.moveto(0,0); mpathxor.lineto(getwidth(),0); mpathxor.lineto(getwidth(),getheight()); mpathxor.lineto(0,getheight()); mpathxor.close(); //以上根据实际的canvas或view的大小,画出相同大小的path即可 mpathxor.op(mpath0, path.op.xor); canvas.clippath(mpathxor); }else { canvas.clippath(mpath0, region.op.xor); }
2.明文http限制
当 targetsdkversion >= build.version_codes.p 时,默认限制了http请求,并出现相关日志:
java.net.unknownserviceexception: cleartext communication to xxx not permitted by network security policy
第一种解决方案:在androidmanifest.xml中application添加如下节点代码
<application android:usescleartexttraffic="true">
第二种解决方案:在res目录新建xml目录,已建的跳过 在xml目录新建一个xml文件network_security_config.xml,然后在androidmanifest.xml中application添加如下节点代码
android:networksecurityconfig="@xml/network_config"
名字随机,内容如下:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartexttrafficpermitted="true" /> </network-security-config>
3.android q中的媒体资源读写
1、扫描系统相册、视频等,图片、视频选择器都是通过contentresolver来提供,主要代码如下:
private static final string[] image_projection = { mediastore.images.media.data, mediastore.images.media.display_name, mediastore.images.media._id, mediastore.images.media.bucket_id, mediastore.images.media.bucket_display_name}; cursor imagecursor = mcontext.getcontentresolver().query( mediastore.images.media.external_content_uri, image_projection, null, null, image_projection[0] + " desc"); string path = imagecursor.getstring(imagecursor.getcolumnindexorthrow(image_projection[0])); string name = imagecursor.getstring(imagecursor.getcolumnindexorthrow(image_projection[1])); int id = imagecursor.getint(imagecursor.getcolumnindexorthrow(image_projection[2])); string folderpath = imagecursor.getstring(imagecursor.getcolumnindexorthrow(image_projection[3])); string foldername = imagecursor.getstring(imagecursor.getcolumnindexorthrow(image_projection[4])); //android q 公有目录只能通过content uri + id的方式访问,以前的file路径全部无效,如果是video,记得换成mediastore.videos if(build.version.sdk_int >= build.version_codes.q){ path = mediastore.images.media .external_content_uri .buildupon() .appendpath(string.valueof(id)).build().tostring(); }
2、判断公有目录文件是否存在,自android q开始,公有目录file api都失效,不能直接通过new file(path).exists();判断公有目录文件是否存在,正确方式如下:
public static boolean isandroidqfileexists(context context, string path){ assetfiledescriptor afd = null; contentresolver cr = context.getcontentresolver(); try { uri uri = uri.parse(path); afd = cr.openassetfiledescriptor(uri, "r"); if (afd == null) { return false; } else { close(afd); } } catch (filenotfoundexception e) { return false; }finally { close(afd); } return true; }
3、copy或者下载文件到公有目录,保存bitmap同理,如download,mime_type类型可以自行参考对应的文件类型,这里只对apk作出说明,从私有目录copy到公有目录demo如下(远程下载同理,只要拿到outputstream即可,亦可下载到私有目录再copy到公有目录):
public static void copytodownloadandroidq(context context, string sourcepath, string filename, string savedirname){ contentvalues values = new contentvalues(); values.put(mediastore.downloads.display_name, filename); values.put(mediastore.downloads.mime_type, "application/vnd.android.package-archive"); values.put(mediastore.downloads.relative_path, "download/" + savedirname.replaceall("/","") + "/"); uri external = mediastore.downloads.external_content_uri; contentresolver resolver = context.getcontentresolver(); uri inserturi = resolver.insert(external, values); if(inserturi == null) { return; } string mfilepath = inserturi.tostring(); inputstream is = null; outputstream os = null; try { os = resolver.openoutputstream(inserturi); if(os == null){ return; } int read; file sourcefile = new file(sourcepath); if (sourcefile.exists()) { // 文件存在时 is = new fileinputstream(sourcefile); // 读入原文件 byte[] buffer = new byte[1444]; while ((read = is.read(buffer)) != -1) { os.write(buffer, 0, read); } } } catch (exception e) { e.printstacktrace(); }finally { close(is,os); } }
4、保存图片相关
/** * 通过mediastore保存,兼容androidq,保存成功自动添加到相册数据库,无需再发送广播告诉系统插入相册 * * @param context context * @param sourcefile 源文件 * @param savefilename 保存的文件名 * @param savedirname picture子目录 * @return 成功或者失败 */ public static boolean saveimagewithandroidq(context context, file sourcefile, string savefilename, string savedirname) { string extension = bitmaputil.getextension(sourcefile.getabsolutepath()); contentvalues values = new contentvalues(); values.put(mediastore.images.media.description, "this is an image"); values.put(mediastore.images.media.display_name, savefilename); values.put(mediastore.images.media.mime_type, "image/png"); values.put(mediastore.images.media.title, "image.png"); values.put(mediastore.images.media.relative_path, "pictures/" + savedirname); uri external = mediastore.images.media.external_content_uri; contentresolver resolver = context.getcontentresolver(); uri inserturi = resolver.insert(external, values); bufferedinputstream inputstream = null; outputstream os = null; boolean result = false; try { inputstream = new bufferedinputstream(new fileinputstream(sourcefile)); if (inserturi != null) { os = resolver.openoutputstream(inserturi); } if (os != null) { byte[] buffer = new byte[1024 * 4]; int len; while ((len = inputstream.read(buffer)) != -1) { os.write(buffer, 0, len); } os.flush(); } result = true; } catch (ioexception e) { result = false; } finally { close(os, inputstream); } return result; }
4.edittext默认不获取焦点,不自动弹出键盘
该问题出现在 targetsdkversion >= build.version_codes.p 情况下,且设备版本为android p以上版本,解决方法在oncreate中加入如下代码,可获得焦点,如需要弹出键盘可延迟一下:
medittext.post(() -> { medittext.requestfocus(); medittext.setfocusable(true); medittext.setfocusableintouchmode(true); });
5.安装apk intent及其它共享文件相关intent
/* * 自android n开始,是通过fileprovider共享相关文件,但是android q对公有目录 file api进行了限制,只能通过uri来操作, * 从代码上看,又变得和以前低版本一样了,只是必须加上权限代码intent.flag_grant_read_uri_permission */ private void installapk() { if(build.version.sdk_int >= build.version_codes.q){ //适配android q,注意mfilepath是通过contentresolver得到的,上述有相关代码 intent intent = new intent(intent.action_view); intent.setdataandtype(uri.parse(mfilepath) ,"application/vnd.android.package-archive"); intent.setflags(intent.flag_activity_new_task); intent.addflags(intent.flag_grant_read_uri_permission); startactivity(intent); return ; } file file = new file(savefilename + "demo.apk"); if (!file.exists()) return; intent intent = new intent(intent.action_view); if (build.version.sdk_int >= build.version_codes.n) { intent.setflags(intent.flag_activity_new_task); intent.addflags(intent.flag_grant_read_uri_permission); uri contenturi = fileprovider.geturiforfile(getapplicationcontext(), "net.oschina.app.provider", file); intent.setdataandtype(contenturi, "application/vnd.android.package-archive"); } else { intent.setdataandtype(uri.fromfile(file), "application/vnd.android.package-archive"); intent.setflags(intent.flag_activity_new_task); } startactivity(intent); }
6.activity透明相关,windowistranslucent属性
android q 又一个天坑,如果你要显示一个半透明的activity,这在android10之前普通样式activity只需要设置windowistranslucent=true即可,但是到了androidq,它没有效果了,而且如果动态设置view.setvisibility(),界面还会出现残影...
解决办法:使用dialog样式activity,且设置windowisfloating=true,此时问题又来了,如果activity根布局没有设置fitssystemwindow=true,默认是没有侵入状态栏的,使界面看上去正常。
7.剪切板兼容
android q中只有当应用处于可交互情况(默认输入法本身就可交互)才能访问剪切板和监听剪切板变化,在onresume回调也无法直接访问剪切板,这么做的好处是避免了一些应用后台疯狂监听响应剪切板的内容,疯狂弹窗。
因此如果还需要监听剪切板,可以使用应用生命周期回调,监听app后台返回,延迟几毫秒访问剪切板,再保存最后一次访问得到的剪切板内容,每次都比较一下是否有变化,再进行下一步操作。
8.第三方分享图片等操作,直接使用文件路径的,如qq图片分享,都需要注意,这是不可行的,都只能通过mediastore等api,拿到uri来操作
这些是我们根据sdk升级到29时遇到的实际问题而罗列出来的,不是翻译androidq中的行为变更,具体问题请根据自身实际自行解决。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Unity UGUI实现滑动翻页效果
下一篇: 新生儿鼻塞怎么办 6大方法缓解新生儿鼻塞