Codeegg’s 845th tweet

Author: Huang Haibin

Link: https://my.oschina.net/huanghaibin/blog/3106432

Size girls see the world

preface

The adaptation of our App was upgraded from targetSdkVersion = 26 to 29, so we encountered a lot of pits. The final version configuration is as follows:

Now enter the pothole fitting guide, which contains practical code and never copy the translated document

1.Region.Op is abnormal

java.lang.IllegalArgumentException: Invalid Region.Op – only INTERSECT and DIFFERENCE are allowed

Call Canvas.clippath (path, region.op.xxx) when targetSdkVersion >= build.version_codes. P; Causes the exception, the reference source code is as follows:

@Deprecatedpublic 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"); }}Copy the code

We can see that when the target version starts from Android P, Canvas.clippath (@nonnull Path Path, @nonnull region.op Op); INTERSECT and Region.Op.DIFFERENCE are compatible. It’s not clear what Google is doing by simply throwing an exception and telling developers to adapt. Almost all blogging solutions are as simple as:

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

But we definitely need some high level logic operation effects what about that? For example, the simulation of page-turning reading effect of novel, the solution is as follows: replace path-op, calculate Path first, 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(); Mpathxor. op(mPath0, path.op.xor); mpathXor. op(mPath0, path.op.xor); canvas.clipPath(mPathXOR); }else { canvas.clipPath(mPath0, Region.Op.XOR); }Copy the code

2. Plaintext HTTP restriction

When targetSdkVersion >= build.version_codes. P, the HTTP request is restricted by default and the related log appears:

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

The first solution: add the following node code to the androidmanifest.xml Application

<application android:usesCleartextTraffic="true">

The second solution is to create an XML directory in the res directory, which has already been skipped. Create an XML file in the XML directory network_security_config. XML, and then add the following node code in the androidmanifest.xml file Application

android:networkSecurityConfig="@xml/network_config"

The name is random and the content is as follows:

<? The XML version = "1.0" encoding = "utf-8"? ><network-security-config> <base-config cleartextTrafficPermitted="true" /></network-security-config>Copy the code

3. Read and write media resources in Android Q(10)

Android Q behavior changes will not be detailed, most blogs on the Internet about Android Q adaptation are talking about behavior changes, we will be based on the actual problems encountered, the actual solution

1. Scan system albums, videos, etc., pictures and video selectors are provided by ContentResolver, and the main code is 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 through Content Uri + ID. The previous File path is invalid. Remember to change mediastore.videosif (build.version.sdk_int >= build.version_codes.q){path = mediastore.images.media .EXTERNAL_CONTENT_URI .buildUpon() .appendPath(String.valueOf(id)).build().toString(); }Copy the code

Since Android Q, the public directory File API is invalid. New File(path).exists() cannot be passed directly. Check whether public directory files exist in the following ways:

public static boolean isAndroidQFileExists(Context context, String path){ if (context == null) { return false; } AssetFileDescriptor afd = null; ContentResolver cr = context.getContentResolver(); try { Uri uri = Uri.parse(path); afd = cr.openAssetFileDescriptor(Uri.parse(path), "r"); if (afd == null) { return false; } else { close(afd); } } catch (FileNotFoundException e) { return false; }finally { close(afd); } return true; }Copy the code

3. Save or Download the file to a public directory and save the Bitmap. For example, Download the MIME_TYPE type can refer to the corresponding file type

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 ()) {// If the file exists is = new FileInputStream(sourceFile); Byte [] buffer = new byte[1444]; while ((read = is.read(buffer)) ! = -1) { os.write(buffer, 0, read); } is.close(); os.close(); } } catch (Exception e) { e.printStackTrace(); } finally { close(is,os); }}Copy the code

4, save pictures related

/** * Save through MediaStore, compatible with AndroidQ, save success automatically add to the album database, No need to send ads telling the system to insert album * * @param context context * @param sourceFile sourceFile * @param saveFileName saved file name * @param saveDirName 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 { Util.close(os, inputStream); } return result; }Copy the code

4.EditText does not get 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 Android P or higher. So far, we have not found any relevant modification from the source code.

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

5.Only fullscreen Activities can request orientation exception

This problem occurs when targetSdkVersion >= build.version_codes.o_mr1, also known as API 27, is used on Android 26 devices. If the orientation of the activity is fixed, the exception will occur, but when we test Android 26 models such as Xiaomi and Meizu, there is no exception, huawei models reported the exception, which is what a god… No way, remove the transparent style or remove the fixed direction code, no other solution

6. Install APK intents and other file-related intents

/** Since Android N, files are shared via the FileProvider, but Android Q has restricted the public directory File API. Intent.flag_grant_read_uri_permission */ private void installApk() {if(build.version.sdk_int >= Build.version_codes.q){// For Android Q, note that mFilePath is obtained by 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 + "osc.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); }Copy the code

WindowIsTranslucent 7

If you want to display a translucent Activity, before Android10, you only need to set windowIsTranslucent=true for normal activities, but in Android10, it doesn’t work. And if you use view.setvisibility (), the interface will be undefined…

If the Activity root layout is not set to fitsSystemWindow=true, the default padddingTop status bar height is set to the root layout. Make the interface look normal, so if your Activity code dynamically ADAPTS the status bar height, you need to set fitsSystemWindow=true to the root layout, otherwise you will find an extra padddingTop status bar height

8. Clipboard compatibility

The clipboard and clipboard changes can only be accessed when the application is in an interactive state, and the clipboard cannot be accessed directly during the onResume callback. The advantage of this is that some background applications do not have to listen to the contents of the response clipboard crazily.

Of course, the Android Q changes are quite large, such as App private sandbox file, location permission and background pop-up Activity restrictions, these must be based on their own practice to step on the pit adaptation, as far as possible to read the official document, reference improvement.

Related articles:

  • How to set the right “flag” for next year

  • Help you Carry the exclusive interview questions

  • Effective Java in Kotlin: Update your Effective Java in Kotlin

Question of the day:

Is your App compatible with Android 10?

Exclusive upgrade community: I Finally Figured it out