android10适配方法,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添加如下节点代码

第二种解决方案:在res目录新建xml目录,已建的跳过 在xml目录新建一个xml文件network_security_config.xml,然后在AndroidManifest.xml中Application添加如下节点代码

android:networkSecurityConfig="@xml/network_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[4] + " 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来操作

目前我们实际遇到的问题就这些

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明
Android开发从入门到精通光盘文件可以包含以下内容: 1. 入门指南:该部分提供了对Android开发环境的介绍,包括安装和配置Android Studio、设置虚拟设备、创建项目等基本知识。 2. Java语言基础:Android开发基于Java语言,因此对于初学者来说,理解Java的基本语法和面向对象编程原则非常重要。该部分可以包含Java的基础知识,如变量、数据类型、循环、条件语句等。 3. Android组件和界面设计:该部分介绍了Android应用程序的基本组成部分,如活动(Activity)、片段(Fragment)、服务(Service)、广播接收器(Broadcast Receiver)等,以及使用XML和Java代码创建用户界面的方法。 4. 数据存储和管理:Android应用程序通常需要存储和管理数据,如使用SQLite数据库、使用SharedPreferences保存应用程序的设置等。该部分可以涵盖数据库操作、文件读写、网络数据传输等相关知识。 5. 多媒体和图形处理:Android开发可以涉及到多媒体和图形处理,如播放音频、视频、显示图像等。该部分可以介绍如何使用Android提供的多媒体和图形处理API,实现各种功能。 6. 网络编程和互联网连接:现代应用程序通常需要与互联网进行交互,获取远程数据或与其他服务进行通信。该部分可以讲解如何进行网络编程,包括HTTP请求、JSON数据解析等。 7. 高级主题:该部分可以涵盖一些高级主题,如并发编程、性能优化、适配不同屏幕尺寸等。这些知识可以帮助开发者更好地理解和优化应用程序。 8. 实际项目和案例分析:该部分可以介绍一些实际的项目和案例分析,帮助开发者将之前学到的知识应用到实际开发中。 通过Android开发从入门到精通光盘文件,开发者可以系统地学习和掌握Android开发的方方面面,从而能够独立地开发出高质量的Android应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值