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

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

(0)

相关推荐

  • Android10开发者常见问题(小结)

    Q 1: 指定 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 好像并不能隐藏导航栏和状态栏? A: 您需要告诉系统您希望的布局方式.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN: 全屏,状态栏浮在应用之上:SYSTEM_UI_FLAG_LAYOUT_STABLE: 将视图保持稳定,使用最极端的布局方式. setSystemUiVisibility() 仅在所指定的视图可见时生效.更具体来说,所指定的视图必须保持可见才能让导航栏保持隐藏. Q 2:

  • 解决Android 10/Android Q手机在后台无法正常定位问题

    Android 10 在2019年9月份正式发布,带来了一个非常重大的GPS权限改变.为用户提供了 仅在使用此应用时允许.一旦用户选择"仅在使用此应用时允许",就会导致APP在后台或者锁屏时候无法正常记录GPS轨迹,这个对像滴滴出行.共享单车.跑步软件影响非常的大. 针对这个变化,Google也给出了新的 解决方案. 第一步:升级SDK 修改build.gradle,升级APP的 compileSdkVersion 和 targetSdkVersion. android { compi

  • 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

  • JavaScript代码编写中各种各样的坑和填坑方法

    坑"这个字,在此的意思是"陷阱".由于 JavaScript "弱语言"的性质,使得其在使用过程中异常的宽松灵活,但也极为容易"中招".这些坑往往隐藏着,所以必须擦亮双眼,才能在学习与应用 JS 的道路上走的一帆风顺. 一.全局变量 JavaScript 通过函数管理作用域.在函数内部声明的变量只在这个函数内部,函数外面不可用.另一方面,全局变量就是在任何函数外面声明的或是未声明直接简单使用的. "未声明直接简单使用"

  • Android ViewPager中显示图片与播放视频的填坑记录

    ViewPager介绍 ViewPager的功能就是可以使视图滑动,就像Lanucher左右滑动那样. ViewPager用于实现多页面的切换效果,该类存在于Google的兼容包android-support-v4.jar里面. ViewPager: 1)ViewPager类直接继承了ViewGroup类,所有它是一个容器类,可以在其中添加其他的view类. 2)ViewPager类需要一个PagerAdapter适配器类给它提供数据. 3)ViewPager经常和Fragment一起使用,并且

  • 浅谈mint-ui 填坑之路

    近期上手vue的移动端项目,舍弃了之前自己相对熟悉的mui框架,改为用饿了么团队为了vue量身定做的mint-ui框架. 之前开发的时候觉得mui的文档就足够坑爹了,但当我开始阅读mint-ui这个文档后才发现自己真是太年轻了... 针对一些自己遇到的问题,特此记录成文档,方便日后使用. swipe组件 因为项目加载eslint的缘故也就没有像之前的项目一样引用swiper框架. 这个轮播图的组件文档实在是不敢恭维(尽管其他的文档也好不到哪里去),官方给出的参数真是少的可怜,一些方法也并没有提到

  • Android—基于微信开放平台v3SDK开发(微信支付填坑)

    接触微信支付之前听说过这是一个坑,,,心里已经有了准备...我以为我没准跳坑出不来了,没有想到我填上了,调用成功之后我感觉公司所有的同事都是漂亮的,隔着北京的大雾霾我仿佛看见了太阳~~~好了,装逼结束...进入正题 开发准备: 1.在微信开放平台申请账号 2.成功后创建应用,就是填一些看似很官方很正经的资料了...(说审核7天左右,没有意外的情况下你的app第二天就审核成功了是不是很开心,有了appid,是不是就可以调用微   信支付了????-------想多了,真的) 3.微信支付是需要额外

  • vue填坑之webpack run build 静态资源找不到的解决方法

    vue cli搭建的项目,在本地测试调试都OK,运行npm run dev之后运行正常,今天放到服务器上跑,结果RD说找不到打包后的静态资源,浏览器控制台错误代码404 问了RD,因为服务器上线方式的调整,不会指定具体项目路径因此,https://bigdata.yiche.com/static/css/app.149f36018149fcbe537f02cafdc6f047.css 这个文件找不到,看看我们正常打包好的目录: 正确的访问路径是:https://bigdata.yiche.com

  • vue中的mescroll搜索运用及各种填坑处理

    父组件处理: <template> <div class="wrap"> <!-- 搜索框 --> <div class="searchInputArea"> <div class="searchBarBox"> <div class="inputWrap" > <form onsubmit="javascript:return false

  • Django2.1集成xadmin管理后台所遇到的错误集锦(填坑)

    django默认是有一个admin的后台管理模块,但是丑,功能也不齐全,但是大神给我们已经集成好了xadmin后台,我们拿来用即可,但是呢,django已经升级到2.1版本了,xadmin貌似跟不上节奏,那么在集成过程中咱就一步一步填坑吧,这也是一种学习的过程,遇到错误,找到错误的地方,看看django最新升级都修改了那些,去掉了那些,把相应出错的地方替换即可. xadmin源码地址:https://github.com/sshwsfc/xadmin 下载并解压: 我们用到的是xadmin文件夹

  • vscode 安装go第三方扩展包填坑记录的详细教程

    1.vscode中安装go扩展包,不再阐述. 2.在安装好go的扩展包以后,创建GOPATH环境变量 3.PATH中会自动添加,如果没有可手动添加 4.在GOPATH目录下创建自己的工作空间(为什么一定是在GOPATH下创建,还不太清楚),我的是workspace(名称可以自定义) 5.打开VSCODE,文件-打开文件夹,选择GOPATH目录 6.在workspace下创建helloworld目录(我称为项目空间) 7.配置VSCODE中的setting.json文件 加入如下配置: 8.编写h

  • 详解pyqt5的UI中嵌入matplotlib图形并实时刷新(挖坑和填坑)

    一.pyqt5的UI中嵌入matplotlib的方法 1.导入模块 导入模块比较简单,首先声明使用pyqt5,通过FigureCanvasQTAgg创建画布,可以将画布的图像显示到UI,相当于pyqt5的一个控件,后面的绘图就建立在这个画布上,然后把这个画布当中pyqt5的控件添加到pyqt5的UI上,其次要导入matplotlib.figure的Figure ,这里要注意的是matplotlib.figure中的Figure,不是matplotlib.pyplot模块中的Figure,要区分清

随机推荐