Guía de adaptación y llenado de pozos de Android10, código de experiencia real, adición continua

Guía de adaptación completa de Android10, que incluye código de experiencia real, nunca copie documentos traducidos

1.Region.Op相关异常:java.lang.IllegalArgumentException: Region.Op no válida: solo se permiten INTERSECT y DIFFERENCE

La excepción causada al llamar a canvas.clipPath(path, Region.Op.XXX ); cuando targetSdkVersion >= Build.VERSION_CODES.P , el código fuente de referencia es el siguiente:

@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");
     }
}

Podemos ver que cuando la versión de destino comienza desde Android P, Canvas.clipPath( @NonNull Path path, @NonNull Region.Op op); ha quedado obsoleto y es una API abandonada que contiene riesgos de excepción. Solo Region.Op.INTERSECT y La región .Op.DIFFERENCE es compatible y casi todas las soluciones de blogs son tan simples y toscas como esta:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    canvas.clipPath(path);
} else {
    canvas.clipPath(path, Region.Op.XOR);// REPLACE、UNION 等
}

Pero, ¿qué pasa si necesitamos algunos efectos de operación lógica avanzados? Por ejemplo, para simular el efecto de lectura de página de una novela, la solución es la siguiente: use Path.op en su lugar, calcule la Ruta primero y luego proporcione 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. Borrar restricciones de texto HTTP

Cuando targetSdkVersion >= Build.VERSION_CODES.P , las solicitudes HTTP están restringidas de forma predeterminada y aparecen registros relevantes:

java.net.UnknownServiceException: CLEARTEXT communication to xxx not permitted by network security policy

La primera solución: agregue el siguiente código de nodo a la aplicación en AndroidManifest.xml

<application android:usesCleartextTraffic="true">

La segunda solución: crear un nuevo directorio xml en el directorio res. Omitir el existente. Crear un nuevo archivo xml network_security_config.xml en el directorio xml y luego agregar el siguiente código de nodo a Aplicación en AndroidManifest.xml.

android:networkSecurityConfig="@xml/network_config"

El nombre es aleatorio y el contenido es el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

3. Lectura y escritura de recursos multimedia en Android Q

1. Escanee álbumes, videos, etc. del sistema. Los selectores de imágenes y videos se proporcionan a través de ContentResolver. El código principal es el siguiente:

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. Determine si el archivo del directorio público existe. Desde Android Q, la API de archivos del directorio público ha dejado de ser válida. No puede determinar directamente si el archivo del directorio público existe a través del nuevo Archivo(ruta).existe();. El método correcto es el siguiente sigue:

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. Copie o descargue el archivo al directorio público. Lo mismo se aplica al guardar el mapa de bits. Por ejemplo, Descargar, el tipo MIME_TYPE puede referirse al tipo de archivo correspondiente. Aquí solo explicamos el APK. Copiar del directorio privado al directorio público demo de la siguiente manera (lo mismo se aplica a la descarga remota, solo obtenga OutputStream, o puede descargarlo a un directorio privado y copiarlo al directorio público):

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. Guarde imágenes relacionadas

 /**
     * 通过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 no obtiene el foco de forma predeterminada y no muestra automáticamente el teclado.

Este problema ocurre cuando targetSdkVersion >= Build.VERSION_CODES.P y la versión del dispositivo es Android P o superior. La solución es agregar el siguiente código a onCreate para ganar foco. Si necesita abrir el teclado, puede retrasarlo :

mEditText.post(() -> {
       mEditText.requestFocus();
       mEditText.setFocusable(true);
       mEditText.setFocusableInTouchMode(true);
});

 5. Instale APK Intent y otros Intents relacionados con archivos compartidos

/*
* 自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.Relacionado con la transparencia de la actividad, atributo windowIsTranslucent

Android Q es otro sumidero. Si desea mostrar una Actividad translúcida, antes de Android 10, la Actividad de estilo normal solo necesita configurar windowIsTranslucent = true, pero en Android Q, no tiene ningún efecto, y si configura dinámicamente View.setVisibility ( ), todavía habrá imágenes residuales en la interfaz...

Solución: utilice Actividad de estilo de diálogo y establezca windowIsFloating=true. El problema vuelve a surgir. Si el diseño raíz de la Actividad no se establece en fitSystemWindow=true, la barra de estado no será invadida de forma predeterminada, lo que hará que la interfaz parezca normal.

7. Compatible con portapapeles

En Android Q, solo cuando la aplicación está en un estado interactivo (el método de entrada predeterminado en sí es interactivo) se puede acceder al portapapeles y monitorear los cambios del portapapeles. No se puede acceder directamente al portapapeles durante la devolución de llamada onResume. La ventaja de esto es que evita algunos El fondo de la aplicación monitorea y responde frenéticamente al contenido del portapapeles y abre ventanas frenéticamente.

Por lo tanto, si aún necesita monitorear el portapapeles, puede usar la devolución de llamada del ciclo de vida de la aplicación, monitorear el retorno en segundo plano de la APLICACIÓN, retrasar el acceso al portapapeles durante unos milisegundos y luego guardar el contenido del portapapeles obtenido del último acceso y comparar si Hay cambios cada vez. Luego continúe con el siguiente paso.

8. Cuando las operaciones de terceros, como compartir imágenes, utilizan directamente rutas de archivos, como compartir imágenes QQ, tenga en cuenta que esto no es factible y solo puede operarse obteniendo el Uri a través de API como MediaStore.

Este artículo está reproducido de: https://blog.csdn.net/huanghaibin_dev/article/details/103237821

Supongo que te gusta

Origin blog.csdn.net/weitao_666/article/details/104060015
Recomendado
Clasificación