欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android10填坑适配指南(实际经验代码)

程序员文章站 2022-05-31 12:44:16
今天看到一篇好的文章,分享给大家,膜拜大佬。 android10填坑适配指南,包含实际经验代码,绝不照搬翻译文档 1.region.op相关异常:java.lang.illeg...

今天看到一篇好的文章,分享给大家,膜拜大佬。

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中的行为变更,具体问题请根据自身实际自行解决。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。