Android10 Pit Filling Adaptation Guide of Practical Experience Code

  • 2021-11-13 18:16:41
  • OfStack

Today, I saw a good article, share it with everyone, and worship the big brothers.

Android10 pit filling adaptation guide, including practical experience code, never copy translated documents

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

When targetSdkVersion > = canvas. clipPath is called when Build.VERSION_CODES. P (path, Region. Op. XXX); The exception caused by the reference source code is 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); Abandoned API, which has been abandoned and contains abnormal risks, is only compatible with Region. Op. INTERSECT and Region. Op. DIFFERENCE. Almost all blog solutions are simple and rude 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 what if we need some advanced logic operations? For example, the simulation page-turning reading effect of novels, the solution is as follows, replacing it with Path. op, first calculating Path, and then


 To 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();
 // Above according to the actual Canvas Or View The size of, draw the same size Path You can 
 mPathXOR.op(mPath0, Path.Op.XOR);
 canvas.clipPath(mPathXOR);
}else {
 canvas.clipPath(mPath0, Region.Op.XOR);
}

2. Clear HTTP restrictions

When targetSdkVersion > = Build.VERSION_CODES.P, the HTTP request is restricted by default and the associated log appears:

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

Solution 1: Add the following node code to Application in AndroidManifest. xml


<application android:usesCleartextTraffic="true">

Solution 2: Create a new xml directory in the res directory, skip creating a new xml file network_security_config. xml in the AndroidManifest directory, and then add the following node code to Application in AndroidManifest. 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. Read and write media resources in Android Q

1. Scan system photo albums, videos, etc. Pictures and video selectors are provided through ContentResolver. 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[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  Public directories can only be passed through Content Uri + id The way to access, the previous File All paths are invalid, if yes Video Remember to replace it with 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 and cannot directly pass new File (path). exists (); The correct way to judge whether a public directory file exists 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 files to the public directory. Save Bitmap in the same way. For example, Download and MIME_TYPE can refer to the corresponding file types by themselves. Here, only APK is explained. From the private directory copy to the public directory demo, it is as follows (remote download is the same, as long as you get OutputStream, you can also download it to the private directory and then copy 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 the file exists 
    is = new FileInputStream(sourceFile); //  Read 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 the picture related


 /**
  *  Pass MediaStore Save, compatible AndroidQ Save successfully and automatically add it to the album database without sending 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 focus by default and does not automatically pop up the keyboard

This problem occurs in targetSdkVersion > = Build.VERSION_CODES. P, and the device version is Android P or above, the solution is to add the following code to onCreate to get the focus. If you need to pop up the keyboard, you can delay 1 time:


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

5. Install APK Intent and other shared file related Intent


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

6. Activity Transparency Dependence, windowIsTranslucent Attributes

Android Q has another sinkhole. If you want to display a translucent Activity, before android10, the ordinary style Activity only needs to set windowIsTranslucent=true, but when AndroidQ, it has no effect, and if View. setVisibility () is dynamically set, the interface will also appear afterimage...

Solution: Use Dialog style Activity, and set windowIsFloating=true. At this time, the problem comes again. If fitsSystemWindow=true is not set in Activity root layout, the status bar is not invaded by default, which makes the interface look normal.

7. Shear board compatibility

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

Therefore, if you still need to monitor the clipboard, you can use the application life cycle callback to monitor the background return of APP, delay accessing the clipboard for several milliseconds, save the clipboard contents obtained from the last visit, compare whether there is any change under 1 every time, and then proceed to the next step.

8. For the third party to share pictures and other operations, if the file path is directly used, such as QQ picture sharing, it should be noted that this is not feasible, and it can only be operated through API such as MediaStore and Uri

These are listed according to the actual problems encountered when upgrading sdk to 29, not the behavior changes in translating AndroidQ. Please solve the specific problems according to your own reality.


Related articles: