This paper contribute authorized by the simplepeng project address: https://github.com/simplepeng/SpiderMan

This is an open source project about Android Crash collection. The project has more than 300 stars. In addition to Crash collection itself, the project also involves long picture sharing, ScrollView screenshot, 6.0 dynamic permissions, 7.0 FileProvider and other functions. It should be helpful to you, so I recommend it to you. The following is the introduction of the project.

A picture is worth a thousand words


In the figure above, we simulate the occurrence of a NullPointerException. The system catches the exception and presents it in an interface.

How to implement

To achieve global exception catching we need to know about an internal interface in Thead, UncaughtExceptionHandler, which was added in JDK1.5.

All we need to customize a class to implement this interface, and is set to the Thread DefaultUncaughtExceptionHandler.

/ / pseudo code public class SpiderMan implements Thread. UncaughtExceptionHandler {private SpiderMan () { Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread t, Throwable ex) { }}Copy the code

UncaughtExceptionHandler catches exceptions that are not caught in the code and calls back to the uncaughtException method.

Advanced operation

Parsing Throwable

private CrashModel parseCrash(Throwable ex) { CrashModel model = new CrashModel(); try { model.setEx(ex); model.setTime(new Date().getTime()); if (ex.getCause() ! = null) { ex = ex.getCause(); } model.setExceptionMsg(ex.getMessage()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); ex.printStackTrace(pw); pw.flush(); String exceptionType = ex.getClass().getName(); if (ex.getStackTrace() ! = null && ex.getStackTrace().length > 0) { StackTraceElement element = ex.getStackTrace()[0]; model.setLineNumber(element.getLineNumber()); model.setClassName(element.getClassName()); model.setFileName(element.getFileName()); model.setMethodName(element.getMethodName()); model.setExceptionType(exceptionType); } model.setFullException(sw.toString()); } catch (Exception e) { return model; } return model; }Copy the code

As shown in the code above, we can parse out a lot of useful information from the Throwable class, including the class where the crash occurred, the number of lines, and the type of exception.

The new page displays Crash information

Intent intent = new Intent(mContext, CrashActivity.class); intent.putExtra(CrashActivity.CRASH_MODEL, model); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent);Copy the code

After parsing the Throwable, we can jump to a new Activity and display information about Crash, where the Context is the Application Context, FLAG_ACTIVITY_NEW_TASK must be used for a successful jump.

Sharing Crash Information


Share the text

Parse the Throwable into useful strings and call the system’s share method

private void shareText(String text) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); Intent.putextra (Intent.extra_subject, "crash info: "); intent.putExtra(Intent.EXTRA_TEXT, text); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); StartActivity (intent.createchooser (Intent, "Share to ")); }Copy the code

Share long figure

Sharing images involves a lot of things, such as ScrollView screenshots, how to save to an Sd card, 6.0 requires dynamic permission detection, 7.0 requires FileProvider compatibility.

ScrollView screenshots

The principle of scrollView screenshot is very simple. It is to create a Bitmap that is the same width and height as the ScrollView and draw the contents of the ScrollView on the Bitmap.

public Bitmap getBitmapByView(ScrollView view) { if (view == null) return null; int height = 0; for (int i = 0; i < view.getChildCount(); i++) { height += view.getChildAt(i).getHeight(); } Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.drawRGB(255, 255, 255); view.draw(canvas); return bitmap; }Copy the code

The above code traverses all the child views of ScrollView, in order to calculate the true height of ScrollView, but in fact, the source code can be analyzed that ScrollView can only have one child view, so it is possible to directly obtain the first child view of ScrollView.

//ScrollView@Overridepublic void addView(View child) { if (getChildCount() > 0) { throw new IllegalStateException("ScrollView can host only one direct child"); } super.addView(child); }Copy the code

The next step is to create a Bitmap of the same width and height as the ScrollView and set it to the Canvas, which draws a white background before drawing the view’s contents onto the Bitmap.

6.0 Dynamic Rights

Android has made major changes to system permissions since 6.0(API 23). Starting from 6.0, some sensitive permissions need to be applied dynamically at the time of use, and users can refuse to authorize access to these permissions. Users can also go to the APP setting page to close authorization. Since we need to save the long picture to the SD card for sharing, we need to read and write the SD card permission, which is also sensitive.

We need to call the requestPermissions method under the ActivityCompat class to requestPermissions.

ActivityCompat.requestPermissions(Activity activity, String[] permissions, int requestCode);Copy the code
  • Activity Indicates the activity for which the permission is currently applied

  • Permissions Specifies the permissions group to be applied for

  • RequestCode request code

Then we need to rewrite onRequestPermissionsResult under the current Activity to determine whether the user authorization we apply for permission.

@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @nonnull int[] grantResults { If (requestCode == REQUEST_CODE) {if (grantResults. Length == 1&& grantResults[0] == PackageManager. PERMISSION_GRANTED) {/ / authorization success shareImage (); } else {// License failed showToast(" Please grant SD card permission to share image "); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); }}Copy the code

There are already a number of excellent permission application frameworks on GayHub that simplify things for us, and there are some ideas that we can learn from. Like the use of a transparent fragments in agent callback permissions apply directly as a result, so we can not rewrite onRequestPermissionsResult method.

Save pictures to SD card

It is also easy to save images to an SD card. Just create a new File and write the Bitmap, but remember to call Recycle when using the Bitmap.

private File BitmapToFile(Bitmap bitmap) { if (bitmap == null) return null; String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) .getAbsolutePath(); File imageFile = new File(path, "spiderMan-" + df.format(model.getTime()) + ".jpg"); try { FileOutputStream out = new FileOutputStream(imageFile); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); out.flush(); out.close(); bitmap.recycle(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return imageFile; }Copy the code
7.0 FileProvider

Before 7.0, we could call uri.fromfile (File) to get the Uri address of the File. But after 7.0 we need FileProvider. What is a FileProvider?

Starting with Android 7.0, in order to improve the security of private directory and prevent application information leakage, developers can no longer simply access an application’s private directory files via file://Uri or allow other applications to access their private directory files.

And since 7.0, StrictMode in the Android SDK forbids developers from exposing the file:// Uri outside the application. When an Intent containing the file:// Uri is used to leave the application, a FileUriExposedException occurs.

To solve this problem, we first create an XML file under the RES/XML directory to store the directory files that the application needs to share.

<? The XML version = "1.0" encoding = "utf-8"? ><paths> <external-path name="image_cache" path="Download" /></paths>Copy the code

We then use a class to extend the FileProvider class. The advantage of this is that there is no conflict between multiple applications using the FileProvider class at the same time.

public class SpiderManFileProvider extends FileProvider {}Copy the code

Then add our custom Provider to androidmanifest.xml. Because FileProvider is also an inheritance and ContentProvider, it belongs to one of the four components, so it must be declared in androidmanifest.xml file.

<application>        <provider            android:name="com.simple.spiderman.SpiderManFileProvider"            android:authorities="${applicationId}.spidermanfileprovider"            android:exported="false"            android:grantUriPermissions="true">            <meta-data                android:name="android.support.FILE_PROVIDER_PATHS"                android:resource="@xml/file_paths" />        </provider></application>Copy the code

Finally, the Intent carries the File Uri and shares the code

Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); intent.setType("image/*"); If (build.version.sdk_int >= build.version_codes.n) {Uri contentUri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + ".spidermanfileprovider", file); intent.putExtra(Intent.EXTRA_STREAM, contentUri); } else { intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); }intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); StartActivity (intent.createchooser (intent.createchooser (intent.createchooser));Copy the code

The source address

https://github.com/simplepeng/SpiderMan

Recommended reading

After reading this Android ANR analysis, go play dumb with your interviewer! After reading this LeakCanary analysis, screw the interviewer! My first live broadcast, 2019 Fighting!

Programming · thinking · workplace welcome scan code attention