Android 10 pit filling adaptation Guide

1.Region.Op related exception: java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed

Call canvas.clipPath(path, Region.Op.XXX ); for the exception caused, refer to the source code as follows:

 

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

We can see that when the target version starts from Android P, canvas.clippath( @NonNull Path path, @NonNull Region.Op op); has been abandoned, and is an abandoned API containing abnormal risks. Only Region.Op.INTERSECT and region.op.division are compatible. Almost all blog solutions are simple and rough as follows:

 

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

But we must need some high-level logic operation effect? For example, for the simulation page turning reading effect of the novel, the solution is as follows: instead of Path.op, first calculate Path, and then give 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();
    //According to the actual size of Canvas or View, draw the same size Path
    mPathXOR.op(mPath0, Path.Op.XOR);
    canvas.clipPath(mPathXOR);
}else {
    canvas.clipPath(mPath0, Region.Op.XOR);
}

2. Clear text HTTP restriction

When targetsdkversion > = build.version? Codes. P, HTTP requests are limited by default, and related logs appear:

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

The first solution: add the following node code in Android manifest.xml

<application android:usesCleartextTraffic="true">

The second solution: create a new XML directory in res directory, skip to create a new XML file in XML Directory network ﹣ security ﹣ config.xml, and then add the following node code in Android manifest.xml

android:networkSecurityConfig="@xml/network_config"

The name is random, and the content is as follows:

 

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

3. Reading and writing of media resources in Android Q

1. Scanning system albums, videos, etc., pictures and video selectors are provided through ContentResolver, and the main codes are as follows:

 

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 public directory can only be accessed by Content Uri + id. previous File paths are all invalid. If it's Video, please change to 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. Judge whether the public directory file exists. Since Android Q, the public directory File API has failed. You can't directly judge whether the public directory file exists through new File(path).exists(). The correct way is as follows:

 

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 or Download the file to the public directory, and save the Bitmap. For example, Download and MIME type can refer to the corresponding file type by themselves. Here, only the APK is explained. The demo from the private directory copy to the public directory is as follows (remote Download is the same, as long as you get the OutputStream, you can also Download it to the private directory and then copy it to the public directory):

 

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()) { // When file exists
                is = new FileInputStream(sourceFile); // Read in the original file
                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. Save picture related

 

 /**
     * Save via MediaStore, Android Q compatible, save successfully and automatically added to the album database, no need to send a broadcast to tell the system to insert the album
     *
     * @param context      context
     * @param sourceFile   source file
     * @param saveFileName Saved file name
     * @param saveDirName  picture Subdirectory
     * @return Success or failure
     */
    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 does not get the focus by default and does not automatically pop up the keyboard

This problem occurs when targetsdkversion > = build.version ﹣ codes. P, and the device version is above Android P. the solution is to add the following code into onCreate to get the focus. If you need to pop up the keyboard, you can delay:

 

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

5. Install APK Intent and other shared file related Intent

 

/*
* Since Android N, related files have been shared through FileProvider. However, Android Q has limited the File API of public directory, which can only be operated through Uri,
* From the code point of view, it becomes the same as the previous lower version, except that the permission code intent.flag ﹣ grant ﹣ read ﹣ URI ﹣ permission must be added
*/ 
private void installApk() {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
            //For Android Q adaptation, note that mFilePath is obtained through ContentResolver, and there are relevant codes above
            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 transparency, windowIsTranslucent property

Android q is another sinkhole. If you want to display a translucent Activity, you only need to set windowIsTranslucent=true for the normal style Activity before Android 10. But when Android Q comes, it has no effect, and if you dynamically set View.setVisibility(), the interface will show the shadow

Solution: use Dialog style Activity and set windowsfloating = true. At this time, the problem comes again. If fitsSystemWindow=true is not set for the root layout of Activity, there is no intrusion into the status bar by default, so that the interface looks normal.

7. Shear board compatible

In Android Q, only when the application is in an interactive situation (the default input method itself can interact) can it access the clipboard and monitor the changes of the clipboard. In the onResume callback, it can't directly access the clipboard. The advantage of this is to avoid some applications' crazy background monitoring response to the contents of the clipboard and crazy pop-up.

Therefore, if you need to monitor the clipboard, you can use the application life cycle callback to monitor the background return of the APP, delay the access to the clipboard for several milliseconds, and then save the contents of the clipboard obtained from the last access. Each time, compare whether there is any change, and then carry out the next operation.

8. For third-party image sharing and other operations, if you directly use the file path, such as QQ image sharing, you need to note that this is not feasible, and you can only use MediaStore and other API s to get the Uri for operation


 

38 original articles published, praised 11, 10000 visitors+
Private letter follow

Tags: Android xml network Java

Posted on Wed, 15 Jan 2020 00:03:55 -0800 by greengo