Writing in the front

Yesterday we also shared 7.0 camera adaptation, today we will talk about Android camera adaptation.

Mention Android call system camera to take photos upload pictures or show pictures, surely any Android friends will not be unfamiliar, the basic function has covered various applications, today, I will give you to talk about online is not much but often hear you laugh at the problem.

Photo function realization

There are two ways to implement the camera function: one is to use the camera API to customize the camera, and the other is to use the Intent to call the camera specified by the system to take a photo. And these two ways to achieve a lot of online search, I will not be verbose here.

Is there a camera available?

As mentioned above, we call the camera APP designated by the system to take photos, so is there any APP that can be called by us in the system? We are not sure about this, after all, There are many weird problems with Android, and we have actually encountered such extreme situations that caused the flash back. Although very extreme, but as a client personnel still have to deal with, there are two ways:

  • When calling the camera, it’s a simple, crude try-catch

  • Before calling the camera, check whether the system has camera APP available

So how do you check if your system has a camera APP available? The system provides us with an API in the PackageManager:

ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE: ACTION_IMAGE_CAPTURE

/** * Check whether the system can start the camera application ** @return yes return true, There is no return false * / public Boolean hasCamera () {PackageManager PackageManager = mActivity. GetPackageManager (); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); return list.size() > 0; }Copy the code

The picture taken is “crooked”!!

We often encounter a situation where we see the picture is positive when we take a picture, but when our APP gets the picture, we find that it has been rotated 90 degrees (it may also be 180 or 270, but 90 degrees is more common, which seems to be caused by the phone sensor). A lot of kids have trouble with this because it doesn’t happen on all phones, and when it does, it doesn’t happen every time. How do you solve this problem? From the perspective of solution, as long as the rotation Angle of the photo is obtained, the Angle can be corrected by using Matrix. So the question is, how do you know the Angle of rotation? Careful children may find that after taking a photo, go to the album and click on the properties view, you will see a bunch of data about the photo’s properties below.Yes, there is an Angle of rotation, and if the image file is rotated after the image is taken, the image properties will tell us how much it was rotated. Once we get this Angle value, we can do the correction work. Android provides itExifInterfaceClass to satisfy the operation of getting various attributes of the picture.throughExifInterfaceClass to get theTAG_ORIENTATIONProperty, which is the rotation Angle we want to find. ReutilizationMatrixIt can be corrected by rotation. The implementation code is roughly as follows:

Public static int getBitmapDegree(String path) {public static int getBitmapDegree(String path) {public static int getBitmapDegree(String path) {public static int getBitmapDegree(String path) {int degree = 0; ExifInterface ExifInterface = new ExifInterface(path); ExifInterface = new ExifInterface(path); / / get the rotation of the image information int orientation. = exifInterface getAttributeInt (exifInterface TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: degree = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: degree = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: degree = 270; break; } } catch (IOException e) { e.printStackTrace(); } return degree; } public static bitmap ** @param degree * @param degree * @return rotated image */ public static bitmap RotateBitmapByDegree (Bitmap Bitmap, int degree) {Matrix Matrix = new Matrix(); rotateBitmapByDegree(Bitmap Bitmap, int degree) { matrix.postRotate(degree); // Rotate the original image according to the rotation matrix, Bitmap newBitmap = bitmap.createBitMap (Bitmap, 0, 0, bitmap.getwidth (), bitmap.getheight (), matrix, true); if (bitmap ! = null && ! bitmap.isRecycled()) { bitmap.recycle(); } return newBitmap; }Copy the code

ExifInterface can get much more information than just the rotation Angle. For other parameters, you can refer to the API documentation.

Why did you flash back after the photo?

I have encountered such a problem on some models of Xiaomi and Meizu. When I called the system camera to take photos, I clicked “yes” and went back to my APP, but somehow I lost it. There is no error log (some machines have no log at all, and some machines have an empty exception error log). On the same machine is not necessary (sometimes how clap do not flash back, sometimes a clap will flash back);

It is always a headache to deal with non-necessary problems, and I was very confused when I met such problems. When an APP enters the camera camera interface through an Intent, the system will destroy the current top-level Activity of the APP and recycle it. (Note: I’ve encountered situations where it’s sometimes quickly recycled, and sometimes it doesn’t happen at all.) To verify this, Log the onDestory() method in the Activity that starts the camera. Sure enough, the onDestory() method is executed when you enter the photo screen. Therefore, the previously mentioned flash backoff can be inferred as the Activity is recycled, causing some non-UI control member variables to be empty. (Some machines will report empty exception error logs, but some machines will flash back and report nothing. Is it weird?) Since the Activity is recycled, the onSaveInstanceState() and onRestoreInstanceState() methods come to mind. Go to onSaveInstanceState() and save the data and restore it in the onRestoreInstanceState() method. The general code idea is as follows:

@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mRestorePhotoFile = mCapturePhotoHelper.getPhoto(); if (mRestorePhotoFile ! = null) { outState.putSerializable(EXTRA_RESTORE_PHOTO, mRestorePhotoFile); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mRestorePhotoFile = (File) savedInstanceState.getSerializable(EXTRA_RESTORE_PHOTO); mCapturePhotoHelper.setPhoto(mRestorePhotoFile); }Copy the code

foronSaveInstanceState()onRestoreInstanceState()The role of the method is not familiar with the children’s shoes, a lot of online information, you can search by yourself. Here, there may be children to ask, this flash back does not guarantee recurrence, how do I know the problem and whether it is fixed? We can go to the developer options to enable the do not retain activities for debugging verification.It is used to preserve activities that are currently in contact with the user, and to destroy and recycle activities that are currently unable to interact with the user. Turn on this debugging option to meet the requirements of verification. When an Activity in your app jumps to the Activity of taking photos, the Activity will be destroyed and recycled by the system immediately. In this way, the scene of flash retreat can be completely regenerated, helping developers to confirm whether the problem is fixed. There are also code implementation issues related to the Activity being destroyed. Suppose there are two activities currently, and there is a Button in the MainActivity. Click to call the system camera to take a picture and display it in the PreviewActivity for preview. There are two implementations:

  • After clicking Button in MainActivity, start the system camera to take photos, and get the photos taken in the onActivityResult() method of MainActivity, and jump to the PreviewActivity interface for effect preview.

  • After clicking Button in the MainActivity, start the PreviewActivity interface. Start the system camera to take a picture in the onCreate() (or onStart(), onResume()) method of the PreviewActivity. Then get the photo taken in the onActivityResult() method of PreviewActivity for preview;

The results of the above two solutions are exactly the same, but the second solution has a big problem. Because the code to start the camera is in onCreate() (or onStart(), onResume()), the PreviewActivity is destroyed when you enter the photo screen, and when you return to PreviewActivity after confirming the photo, The destroyed PreviewActivity needs to be rebuilt, onCreate(), onStart(), onResume(), and call the code to start the camera again. To avoid driving your users crazy, be smart about plan 1. When the above situation is mentioned, the Activity will be recovered when the system is called to take photos. It has a high probability of appearing on Mi 4S and MI 4 LTE (MIUI version 7.3, Android version 6.0). Therefore, it is recommended to see this article’s children’s shoes can also go to verify the adaptation.

Image cannot be displayed

The picture can not show this problem is slightly wrong, how to pit method? Down below, this is also a highly probable scenario on mi 4S and MI 4 LTE (MIUI 7.3, Android 6.0) (of course, there is no guarantee that other phones will not appear). According to the business scene mentioned above, after the camera is called to take photos, our APP will have an interface for previewing pictures. But in the use of millet machine to take photos, their APP preview interface but how also can not show the photos, is also quite depressed, depressed after or step by step to troubleshoot the problem! To do this, take a step by step guess and verify the problem.

  • Guess 1: Did not get the photo path, so can not display? Direct breakpoint play log trace, guess a quickly overturned path is there.

  • Guess 2: The Bitmap is too big to display? After the event the renderer is uploaded into a texture. After the event the renderer is uploaded at the end of the day

Every time you take a photo, you get a log like this, and sure enough, the image is too big to display in ImageView. InSampleSize = inSampleSize = inSampleSize = inSampleSize Heaven and earth conscience ah, absolutely do processing, directly look at the code:

/** * compress Bitmap size ** @param imagePath image file path * @param requestWidth compressed to desired width * @param requestHeight compressed to desired height * @return */ public static Bitmap decodeBitmapFromFile(String imagePath, int requestWidth, int requestHeight) { if (! TextUtils.isEmpty(imagePath)) { if (requestWidth <= 0 || requestHeight <= 0) { Bitmap bitmap = BitmapFactory.decodeFile(imagePath); return bitmap; } BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmapfactory. decodeFile(imagePath, options); options.inSampleSize = calculateInSampleSize(options, requestWidth, requestHeight); / / calculation for new sampling rate options. InJustDecodeBounds = false; return BitmapFactory.decodeFile(imagePath, options); } else { return null; } } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; Log.i(TAG, "height: " + height); Log.i(TAG, "width: " + width); if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } long totalPixels = width * height / inSampleSize; final long totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels > totalReqPixelsCap) { inSampleSize *= 2; totalPixels /= 2; } } return inSampleSize; }Copy the code

After looking at the code, do you think there are no problems? Yes, inSampleSize is definitely being processed, so why is the image still too big to show? Is inSampleSize too small because requestWidth and requestHeight are set too large? That’s impossible! I tried to set the length and width to 100 and it still doesn’t work! Simply print the inSampleSize value, and the inSampleSize value is 1. I go, thoroughly hit the face, obviously said good processing, incredibly or 1 !!!! To find out, add log.

public static Bitmap decodeBitmapFromFile(String imagePath, int requestWidth, int requestHeight) { if (! TextUtils.isEmpty(imagePath)) { Log.i(TAG, "requestWidth: " + requestWidth); Log.i(TAG, "requestHeight: " + requestHeight); if (requestWidth <= 0 || requestHeight <= 0) { Bitmap bitmap = BitmapFactory.decodeFile(imagePath); return bitmap; } BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmapfactory. decodeFile(imagePath, options); Log.i(TAG, "original height: " + options.outHeight); Log.i(TAG, "original width: " + options.outWidth); options.inSampleSize = calculateInSampleSize(options, requestWidth, requestHeight); Log. I (TAG, "inSampleSize: "+ options.insamplesize); options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(imagePath, options); } else { return null; }}Copy the code

The log output is as follows:

The original width and height of the picture are -1, what a strange thing! No wonder inSampleSize is still 1 after processing. After the hard teasing, always come back to solve the problem. So, the width and height of the picture are lost, where can I find it? Like this, right?

public static Bitmap decodeBitmapFromFile(String imagePath, int requestWidth, int requestHeight) { ... BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap = bitmapFactory. decodeFile(imagePath, options); bitmap.getWidth(); bitmap.getHeight(); . } else { return null; }}Copy the code

No, it doesn’t work.inJustDecodeBounds = trueWhen,BitmapFactoryThe Bitmap object is null; So how do you get the width and height of the picture? The aforementionedExifInterfaceAgain helping us a lot, the following two properties can be used to get the true width and height of the image.

Why not TAG_IMAGE_HEIGHT but TAG_IMAGE_LENGTH? The improved code is implemented as follows:

public static Bitmap decodeBitmapFromFile(String imagePath, int requestWidth, int requestHeight) { if (! TextUtils.isEmpty(imagePath)) { Log.i(TAG, "requestWidth: " + requestWidth); Log.i(TAG, "requestHeight: " + requestHeight); if (requestWidth <= 0 || requestHeight <= 0) { Bitmap bitmap = BitmapFactory.decodeFile(imagePath); return bitmap; } BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmapfactory. decodeFile(imagePath, options); Log.i(TAG, "original height: " + options.outHeight); Log.i(TAG, "original width: " + options.outWidth); if (options.outHeight == -1 || options.outWidth == -1) { try { ExifInterface exifInterface = new ExifInterface(imagePath); int height = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, ExifInterface.ORIENTATION_NORMAL); The height of the / / get photo int width = exifInterface. GetAttributeInt (exifInterface. TAG_IMAGE_WIDTH, exifInterface. ORIENTATION_NORMAL); Log. I (TAG, "exif height: "+ height); Log.i(TAG, "exif width: " + width); options.outWidth = width; options.outHeight = height; } catch (IOException e) { e.printStackTrace(); } } options.inSampleSize = calculateInSampleSize(options, requestWidth, requestHeight); Log. I (TAG, "inSampleSize: "+ options.insamplesize); options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(imagePath, options); } else { return null; }}Copy the code

Let’s look at the printed log

The above summed up so many children’s shoes around often asked, but online and rarely seen adaptation problems, I hope to help some of the development of children’s shoes less detour. MIUI is mentioned many times in the article, which does not mean that MIUI is the only one with such problems. It is just because most of the phones I have around me are MIUI. When search engines fail to provide much useful information, we rely on breakpoints, logging, console logs, and API documentation to find clues, which are more difficult than solutions. As for why the basic full text copy, because I think the author has spoken particularly clear, I do not need to do twice repeat, but also to share with you. So let me fill in a different development scenario.

If a colleague has written a compressed nine-grid display picture?

Now that your framework is ready, you just need to return the path of a file, you might say :(all the code below is in the onActivityResult() ‘method)

if (null ! = imageGridAdapter.uri) { final String url = PhotoUtil.getImageUrlFromActivityResult(this, imageGridAdapter.uri); // Get the image's path log. e(TAG, "onActivityResult: url:" + url); if (! TextUtils.isEmpty(url)) { file = new File(url); size = file.length(); Log.e(TAG, "onActivityResult: size:" + size); If (size > 0){ImageItem ImageItem = new ImageItem(); ImageItem. ImageId = imageItem. NEW_ID; ImageItem. PhotoPath = url; ImageGridAdapter. GetmDataList (). The add (imageItem); ImageGridAdapter. NotifyDataSetChanged (); imageGridAdapter.uri = null; }}}Copy the code

If the size of the file is greater than 0, the image exists, the image will be displayed, and the UI refresh will be prompted. Well, yes, most phones pass the test, but it appears on some of the MIUI systems that suck, return size 0, not enter the judgment loop, naturally won’t show the image. At this time certainly want to go up to Google search on a circle, a circle comes down to harvest many, did not find the real solution however. The size of this file is 0, but the time interval can be a certain size of the new file, and this time is not fixed.

It can be seen that the small partner who meets this pit is also dedicated to doing his best. Therefore, I also tried this method. Considering that the main thread cannot sleep, I used a new thread to delay the processing, and used the loading box. Once the size is not 0, I will display the processing. There may be a problem with the image, so we set a maximum sleep time. Where flag and time are global variables.

flag = true; time = 0; final String url = PhotoUtil.getImageUrlFromActivityResult(this, imageGridAdapter.uri); If (size <= 0) {// If (size < 0) Thread(new Runnable() {@override public void run() {runOnUiThread(new Runnable() {@override public void run() run() { showLoading(SendQuestionActivity.this); }}); While (flag) {// Set the delay step to 0.5s try {thread.sleep (500); Time + = 0.5; file = null; file = new File(url); size = file.length(); Log.e(TAG, "onActivityResult: size:" + size); if (size > 0 || time >= 10) { flag = false; } } catch (InterruptedException e) { e.printStackTrace(); } } runOnUiThread(new Runnable() { @Override public void run() { stopLoading(); }}); } }).start(); } ImageItem imageItem = new ImageItem(); imageItem.ImageId = ImageItem.NEW_ID; imageItem.PhotoPath = url; imageGridAdapter.getmDataList().add(imageItem); imageGridAdapter.notifyDataSetChanged(); imageGridAdapter.uri = null;Copy the code

This problem is solved, but when the user cancels the photo, the size is also 0, which is no doubt to gild the lily, the user cancels the photo will display the loading box for 10 seconds, think terrible!!

I heard that adding the following sentence in front of the new File can solve the problem, but it does not have any use.

// For Mi 4 LTE MIUI 7.0, the size of file is always 0; Try {ExifInterface ExifInterface = new ExifInterface(URL); } catch (Exception e) { e.printStackTrace(); }Copy the code

Well, wait. As mentioned above, ExifInterface can get the width and height parameters of the picture, so can I directly judge whether the user takes a picture by judging whether the width and height of the picture is 0? If width and height are 0, it indicates that the user has canceled taking the photo and will not display it. Otherwise show pictures, action is better than heart, directly on the code.

flag = true; Log.e(TAG, "onActivityResult: uri:" + imagegridAdapter.uri); if (null ! = imageGridAdapter.uri) { final String url = PhotoUtil.getImageUrlFromActivityResult(this, imageGridAdapter.uri); Log.e(TAG, "onActivityResult: url:" + url); boolean flag = true; // For Mi 4 LTE MIUI 7.0, the size of file is always 0; Try {ExifInterface ExifInterface = new ExifInterface(URL); int height = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, ExifInterface.ORIENTATION_NORMAL); The height of the / / get photo int width = exifInterface. GetAttributeInt (exifInterface. TAG_IMAGE_WIDTH, exifInterface. ORIENTATION_NORMAL); Log.e(TAG, "onActivityResult: height:" + height); Log.e(TAG, "onActivityResult: width:" + width); if (height == 0 && width == 0) { flag = false; } } catch (Exception e) { e.printStackTrace(); } if (! TextUtils.isEmpty(url)) { file = new File(url); size = file.length(); Log.e(TAG, "onActivityResult: size:" + size); /** * The above solution can not be solved. After testing, it is found that the size is not 0 after a certain period of time. */ if (flag) {ImageItem ImageItem = new ImageItem(); imageItem.ImageId = ImageItem.NEW_ID; imageItem.PhotoPath = url; imageGridAdapter.getmDataList().add(imageItem); imageGridAdapter.notifyDataSetChanged(); imageGridAdapter.uri = null; }}}Copy the code

OK, finally solved. I hope I can help you!

Welcome to follow my technical official account (search nanchen) and share Android resources every day.