├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── clock │ └── utils │ └── ApplicationTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── clock │ │ └── utils │ │ ├── bitmap │ │ └── BitmapUtils.java │ │ ├── common │ │ ├── RuleUtils.java │ │ └── SystemUtils.java │ │ ├── crash │ │ └── CrashExceptionHandler.java │ │ ├── file │ │ └── FileUtils.java │ │ └── text │ │ └── StringUtils.java └── res │ └── values │ └── strings.xml └── test └── java └── com └── clock └── utils └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidUtils 2 | 3 | 本库主要整理汇总自己平常做Android开发经常会用到的代码。方便己迁移到各大项目的开发。 4 | 5 | 如果你对本库的代码感兴趣,欢迎拿走。如果有bug,欢迎在Github上给我提个Issue反馈一下,我会尽快修复。 6 | 7 | ## 当前已有的类 8 | 9 | - BitmapUtils:Bitmap常用操作处理,如获取旋转角度、计算inSampleSize值等 10 | 11 | - CrashExceptionHandler:捕获app奔溃异常,并将其奔溃日志信息生成到本地SD卡上,也可以回传到服务器 12 | 13 | - RuleUtils:尺寸大小转换工具类,如dp,sp转换成为对应设备上的px值 14 | 15 | - SystemUtils:系统实用工具类,如获取手机设备制造商,系统版本号,app版本号,以及设备id(IMEI)等 16 | 17 | - StringUtils:字符串处理类,目前只有将字符串进行MD5转换的功能 18 | 19 | - FileUtils:目前暂无任何功能 -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 8 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | testCompile 'junit:junit:4.12' 24 | compile 'com.android.support:appcompat-v7:23.1.1' 25 | } 26 | -------------------------------------------------------------------------------- /proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android-Studio\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /src/androidTest/java/com/clock/utils/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/clock/utils/bitmap/BitmapUtils.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils.bitmap; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.res.Resources; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Matrix; 10 | import android.media.ExifInterface; 11 | import android.net.Uri; 12 | import android.provider.MediaStore; 13 | import android.text.TextUtils; 14 | import android.util.Log; 15 | 16 | import java.io.BufferedOutputStream; 17 | import java.io.File; 18 | import java.io.FileDescriptor; 19 | import java.io.FileNotFoundException; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.text.SimpleDateFormat; 23 | import java.util.Date; 24 | 25 | /** 26 | * Bitmap操作常用工具类 27 | * Created by Clock on 2015/12/31. 28 | */ 29 | public class BitmapUtils { 30 | 31 | private final static String TAG = BitmapUtils.class.getCanonicalName(); 32 | public final static String JPG_SUFFIX = ".jpg"; 33 | private final static String TIME_FORMAT = "yyyyMMddHHmmss"; 34 | 35 | /** 36 | * 显示图片到相册 37 | * 38 | * @param context 39 | * @param photoFile 要保存的图片文件 40 | */ 41 | public static void displayToGallery(Context context, File photoFile) { 42 | if (photoFile == null || !photoFile.exists()) { 43 | return; 44 | } 45 | String photoPath = photoFile.getAbsolutePath(); 46 | String photoName = photoFile.getName(); 47 | // 其次把文件插入到系统图库 48 | try { 49 | ContentResolver contentResolver = context.getContentResolver(); 50 | MediaStore.Images.Media.insertImage(contentResolver, photoPath, photoName, null); 51 | } catch (FileNotFoundException e) { 52 | e.printStackTrace(); 53 | } 54 | // 最后通知图库更新 55 | context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + photoPath))); 56 | } 57 | 58 | /** 59 | * 将Bitmap保存到指定目录下 60 | * 61 | * @param bitmap 62 | * @param folder 63 | * @return 保存成功,返回其对应的File,保存失败则返回null 64 | */ 65 | public static File saveToFile(Bitmap bitmap, File folder) { 66 | String fileName = new SimpleDateFormat(TIME_FORMAT).format(new Date());//直接以当前时间戳作为文件名 67 | return saveToFile(bitmap, folder, fileName); 68 | } 69 | 70 | /** 71 | * 将Bitmap保存到指定目录下,并且指定好文件名 72 | * 73 | * @param bitmap 74 | * @param folder 75 | * @param fileName 指定的文件名包含后缀 76 | * @return 保存成功,返回其对应的File,保存失败则返回null 77 | */ 78 | public static File saveToFile(Bitmap bitmap, File folder, String fileName) { 79 | if (bitmap != null) { 80 | if (!folder.exists()) { 81 | folder.mkdir(); 82 | } 83 | File file = new File(folder, fileName + JPG_SUFFIX); 84 | if (file.exists()) { 85 | file.delete(); 86 | } 87 | try { 88 | file.createNewFile(); 89 | BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); 90 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos); 91 | bos.flush(); 92 | bos.close(); 93 | return file; 94 | } catch (IOException e) { 95 | e.printStackTrace(); 96 | return null; 97 | } 98 | } else { 99 | return null; 100 | } 101 | } 102 | 103 | /** 104 | * 获取图片的旋转角度 105 | * 106 | * @param path 图片绝对路径 107 | * @return 图片的旋转角度 108 | */ 109 | public static int getBitmapDegree(String path) { 110 | int degree = 0; 111 | try { 112 | // 从指定路径下读取图片,并获取其EXIF信息 113 | ExifInterface exifInterface = new ExifInterface(path); 114 | // 获取图片的旋转信息 115 | int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 116 | switch (orientation) { 117 | case ExifInterface.ORIENTATION_ROTATE_90: 118 | degree = 90; 119 | break; 120 | case ExifInterface.ORIENTATION_ROTATE_180: 121 | degree = 180; 122 | break; 123 | case ExifInterface.ORIENTATION_ROTATE_270: 124 | degree = 270; 125 | break; 126 | } 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | } 130 | return degree; 131 | } 132 | 133 | /** 134 | * 将图片按照指定的角度进行旋转 135 | * 136 | * @param bitmap 需要旋转的图片 137 | * @param degree 指定的旋转角度 138 | * @return 旋转后的图片 139 | */ 140 | public static Bitmap rotateBitmapByDegree(Bitmap bitmap, int degree) { 141 | // 根据旋转角度,生成旋转矩阵 142 | Matrix matrix = new Matrix(); 143 | matrix.postRotate(degree); 144 | // 将原始图片按照旋转矩阵进行旋转,并得到新的图片 145 | Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); 146 | if (bitmap != null && !bitmap.isRecycled()) { 147 | bitmap.recycle(); 148 | } 149 | return newBitmap; 150 | } 151 | 152 | /** 153 | * 压缩Bitmap的大小 154 | * 155 | * @param imageFile 图片文件 156 | * @param requestWidth 压缩到想要的宽度 157 | * @param requestHeight 压缩到想要的高度 158 | * @return 159 | */ 160 | public static Bitmap decodeBitmapFromFile(File imageFile, int requestWidth, int requestHeight) { 161 | if (imageFile != null) { 162 | return decodeBitmapFromFile(imageFile.getAbsolutePath(), requestWidth, requestHeight); 163 | } else { 164 | return null; 165 | } 166 | } 167 | 168 | /** 169 | * 压缩Bitmap的大小 170 | * 171 | * @param imagePath 图片文件路径 172 | * @param requestWidth 压缩到想要的宽度 173 | * @param requestHeight 压缩到想要的高度 174 | * @return 175 | */ 176 | public static Bitmap decodeBitmapFromFile(String imagePath, int requestWidth, int requestHeight) { 177 | if (!TextUtils.isEmpty(imagePath)) { 178 | Log.i(TAG, "requestWidth: " + requestWidth); 179 | Log.i(TAG, "requestHeight: " + requestHeight); 180 | if (requestWidth <= 0 || requestHeight <= 0) { 181 | Bitmap bitmap = BitmapFactory.decodeFile(imagePath); 182 | return bitmap; 183 | } 184 | BitmapFactory.Options options = new BitmapFactory.Options(); 185 | options.inJustDecodeBounds = true;//不加载图片到内存,仅获得图片宽高 186 | BitmapFactory.decodeFile(imagePath, options); 187 | Log.i(TAG, "original height: " + options.outHeight); 188 | Log.i(TAG, "original width: " + options.outWidth); 189 | if (options.outHeight == -1 || options.outWidth == -1) { 190 | try { 191 | ExifInterface exifInterface = new ExifInterface(imagePath); 192 | int height = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, ExifInterface.ORIENTATION_NORMAL);//获取图片的高度 193 | int width = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, ExifInterface.ORIENTATION_NORMAL);//获取图片的宽度 194 | Log.i(TAG, "exif height: " + height); 195 | Log.i(TAG, "exif width: " + width); 196 | options.outWidth = width; 197 | options.outHeight = height; 198 | } catch (IOException e) { 199 | e.printStackTrace(); 200 | } 201 | } 202 | options.inSampleSize = calculateInSampleSize(options, requestWidth, requestHeight); //计算获取新的采样率 203 | Log.i(TAG, "inSampleSize: " + options.inSampleSize); 204 | options.inJustDecodeBounds = false; 205 | return BitmapFactory.decodeFile(imagePath, options); 206 | 207 | } else { 208 | return null; 209 | } 210 | } 211 | 212 | /** 213 | * Decode and sample down a bitmap from resources to the requested width and height. 214 | * 215 | * @param res The resources object containing the image data 216 | * @param resId The resource id of the image data 217 | * @param reqWidth The requested width of the resulting bitmap 218 | * @param reqHeight The requested height of the resulting bitmap 219 | * @return A bitmap sampled down from the original with the same aspect ratio and dimensions 220 | * that are equal to or greater than the requested width and height 221 | */ 222 | public static Bitmap decodeBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { 223 | 224 | // BEGIN_INCLUDE (read_bitmap_dimensions) 225 | // First decode with inJustDecodeBounds=true to check dimensions 226 | final BitmapFactory.Options options = new BitmapFactory.Options(); 227 | options.inJustDecodeBounds = true; 228 | BitmapFactory.decodeResource(res, resId, options); 229 | 230 | // Calculate inSampleSize 231 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 232 | // END_INCLUDE (read_bitmap_dimensions) 233 | 234 | // Decode bitmap with inSampleSize set 235 | options.inJustDecodeBounds = false; 236 | return BitmapFactory.decodeResource(res, resId, options); 237 | } 238 | 239 | /** 240 | * Decode and sample down a bitmap from a file input stream to the requested width and height. 241 | * 242 | * @param fileDescriptor The file descriptor to read from 243 | * @param reqWidth The requested width of the resulting bitmap 244 | * @param reqHeight The requested height of the resulting bitmap 245 | * @return A bitmap sampled down from the original with the same aspect ratio and dimensions 246 | * that are equal to or greater than the requested width and height 247 | */ 248 | public static Bitmap decodeBitmapFromDescriptor(FileDescriptor fileDescriptor, int reqWidth, int reqHeight) { 249 | 250 | // First decode with inJustDecodeBounds=true to check dimensions 251 | final BitmapFactory.Options options = new BitmapFactory.Options(); 252 | options.inJustDecodeBounds = true; 253 | BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); 254 | 255 | // Calculate inSampleSize 256 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 257 | 258 | // Decode bitmap with inSampleSize set 259 | options.inJustDecodeBounds = false; 260 | 261 | return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); 262 | } 263 | 264 | /** 265 | * Google官方代码,计算合适的采样率 266 | * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding 267 | * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates 268 | * the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap 269 | * having a width and height equal to or larger than the requested width and height. 270 | * 271 | * @param options An options object with out* params already populated (run through a decode* 272 | * method with inJustDecodeBounds==true 273 | * @param reqWidth The requested width of the resulting bitmap 274 | * @param reqHeight The requested height of the resulting bitmap 275 | * @return The value to be used for inSampleSize 276 | */ 277 | public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 278 | // BEGIN_INCLUDE (calculate_sample_size) 279 | // Raw height and width of image 280 | final int height = options.outHeight; 281 | final int width = options.outWidth; 282 | int inSampleSize = 1; 283 | 284 | if (height > reqHeight || width > reqWidth) { 285 | 286 | final int halfHeight = height / 2; 287 | final int halfWidth = width / 2; 288 | 289 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 290 | // height and width larger than the requested height and width. 291 | while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { 292 | inSampleSize *= 2; 293 | } 294 | 295 | // This offers some additional logic in case the image has a strange 296 | // aspect ratio. For example, a panorama may have a much larger 297 | // width than height. In these cases the total pixels might still 298 | // end up being too large to fit comfortably in memory, so we should 299 | // be more aggressive with sample down the image (=larger inSampleSize). 300 | 301 | long totalPixels = width * height / inSampleSize; 302 | 303 | // Anything more than 2x the requested pixels we'll sample down further 304 | final long totalReqPixelsCap = reqWidth * reqHeight * 2; 305 | 306 | while (totalPixels > totalReqPixelsCap) { 307 | inSampleSize *= 2; 308 | totalPixels /= 2; 309 | } 310 | } 311 | return inSampleSize; 312 | // END_INCLUDE (calculate_sample_size) 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/main/java/com/clock/utils/common/RuleUtils.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils.common; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.util.TypedValue; 6 | 7 | /** 8 | * 尺寸大小实用工具类 9 | *

10 | * Created by Clock on 2016/1/16. 11 | */ 12 | public class RuleUtils { 13 | 14 | /** 15 | * 获取屏幕的宽度 16 | * 17 | * @param context 18 | * @return 19 | */ 20 | public static int getScreenWidth(Context context) { 21 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 22 | return displayMetrics.widthPixels; 23 | } 24 | 25 | /** 26 | * 获取屏幕的高度 27 | * 28 | * @param context 29 | * @return 30 | */ 31 | public static int getScreenHeight(Context context) { 32 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 33 | return displayMetrics.heightPixels; 34 | } 35 | 36 | /** 37 | * 将dp转换成对应的像素值 38 | * 39 | * @param context 40 | * @param dp 41 | * @return 42 | */ 43 | public static float convertDp2Px(Context context, int dp) { 44 | DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 45 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); 46 | } 47 | 48 | /** 49 | * 将sp转换成对应的像素值 50 | * 51 | * @param context 52 | * @param sp 53 | * @return 54 | */ 55 | public static float convertSp2Px(Context context, int sp) { 56 | DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 57 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/clock/utils/common/SystemUtils.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils.common; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.net.ConnectivityManager; 8 | import android.net.NetworkInfo; 9 | import android.os.Environment; 10 | import android.provider.Settings; 11 | import android.telephony.TelephonyManager; 12 | import android.text.TextUtils; 13 | 14 | /** 15 | * 系统实用工具类 16 | *

17 | * Created by Clock on 2016/1/24. 18 | */ 19 | public class SystemUtils { 20 | 21 | /** 22 | * 获取设备的制造商 23 | * 24 | * @return 设备制造商 25 | */ 26 | public static String getDeviceManufacture() { 27 | return android.os.Build.MANUFACTURER; 28 | } 29 | 30 | /** 31 | * 获取设备名称 32 | * 33 | * @return 设备名称 34 | */ 35 | public static String getDeviceName() { 36 | return android.os.Build.MODEL; 37 | } 38 | 39 | /** 40 | * 获取系统版本号 41 | * 42 | * @return 系统版本号 43 | */ 44 | public static String getSystemVersion() { 45 | return android.os.Build.VERSION.RELEASE; 46 | } 47 | 48 | /** 49 | * 获取设备号 50 | * 51 | * @param context 52 | * @return 53 | */ 54 | public static String getDeviceIMEI(Context context) { 55 | TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 56 | if (telephonyManager == null || TextUtils.isEmpty(telephonyManager.getDeviceId())) { 57 | return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); 58 | } else { 59 | return telephonyManager.getDeviceId(); 60 | } 61 | } 62 | 63 | /** 64 | * 获取应用的版本号 65 | * 66 | * @param context 67 | * @return 68 | */ 69 | public static String getAppVersion(Context context) { 70 | PackageManager packageManager = context.getPackageManager(); 71 | PackageInfo packageInfo; 72 | try { 73 | packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); 74 | return packageInfo.versionName; 75 | } catch (PackageManager.NameNotFoundException e) { 76 | e.printStackTrace(); 77 | } 78 | return null; 79 | } 80 | 81 | /** 82 | * 判断当前有没有网络连接 83 | * 84 | * @param context 85 | * @return 86 | */ 87 | public static boolean getNetworkState(Context context) { 88 | ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 89 | NetworkInfo networkinfo = manager.getActiveNetworkInfo(); 90 | if (networkinfo == null || !networkinfo.isAvailable()) { 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | /** 97 | * SD卡是否挂载 98 | * 99 | * @return 100 | */ 101 | public static boolean mountedSdCard() { 102 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/clock/utils/crash/CrashExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils.crash; 2 | 3 | import android.content.Context; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | import android.widget.Toast; 7 | 8 | import com.clock.utils.common.SystemUtils; 9 | 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | import java.io.OutputStreamWriter; 14 | import java.io.PrintWriter; 15 | import java.io.RandomAccessFile; 16 | import java.text.SimpleDateFormat; 17 | import java.util.Date; 18 | 19 | /** 20 | * app奔溃异常处理器 21 | *

22 | * 使用此类需要在AndroidManifest.xml配置以下权限 23 | *

24 | * android.permission.READ_EXTERNAL_STORAGE 25 | *

26 | * android.permission.WRITE_EXTERNAL_STORAGE 27 | *

28 | * android.permission.READ_PHONE_STATE 29 | *

30 | * Created by Clock on 2016/1/24. 31 | */ 32 | public class CrashExceptionHandler implements Thread.UncaughtExceptionHandler { 33 | 34 | private final static String TAG = CrashExceptionHandler.class.getSimpleName(); 35 | 36 | private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); 37 | 38 | private Context mApplicationContext; 39 | /** 40 | * 保存闪退日志的文件目录 41 | */ 42 | private File mCrashInfoFolder; 43 | /** 44 | * 向远程服务器发送错误信息 45 | */ 46 | private CrashExceptionRemoteReport mCrashExceptionRemoteReport; 47 | 48 | /** 49 | * @param context 50 | * @param crashInfoFolder 保存闪退日志的文件夹目录 51 | */ 52 | public CrashExceptionHandler(Context context, File crashInfoFolder) { 53 | this.mApplicationContext = context.getApplicationContext(); 54 | this.mCrashInfoFolder = crashInfoFolder; 55 | } 56 | 57 | @Override 58 | public void uncaughtException(Thread thread, Throwable ex) { 59 | ex.printStackTrace(); 60 | handleException(ex); 61 | try { 62 | Thread.sleep(3000); 63 | } catch (InterruptedException e) { 64 | e.printStackTrace(); 65 | } 66 | //杀死进程 67 | android.os.Process.killProcess(android.os.Process.myPid()); 68 | } 69 | 70 | /** 71 | * 配置远程传回log到服务器的设置 72 | * 73 | * @param crashExceptionRemoteReport 74 | */ 75 | public void configRemoteReport(CrashExceptionRemoteReport crashExceptionRemoteReport) { 76 | this.mCrashExceptionRemoteReport = crashExceptionRemoteReport; 77 | } 78 | 79 | /** 80 | * 处理异常 81 | * 82 | * @param ex 83 | */ 84 | private void handleException(Throwable ex) { 85 | if (ex == null) { 86 | return; 87 | } else { 88 | saveCrashInfoToFile(ex); 89 | sendCrashInfoToServer(ex); 90 | 91 | //使用Toast来显示异常信息 92 | new Thread() { 93 | @Override 94 | public void run() { 95 | Looper.prepare(); 96 | try { 97 | Toast.makeText(mApplicationContext, "程序出现异常 , 即将退出....", Toast.LENGTH_LONG).show(); 98 | } catch (Exception ex) { 99 | ex.printStackTrace(); 100 | } 101 | Looper.loop(); 102 | } 103 | }.start(); 104 | } 105 | } 106 | 107 | /** 108 | * 保存闪退信息到本地文件中 109 | * 110 | * @param ex 111 | */ 112 | private void saveCrashInfoToFile(Throwable ex) { 113 | try { 114 | if (mCrashInfoFolder == null) { 115 | return; 116 | } 117 | 118 | if (!mCrashInfoFolder.exists()) {//闪退日志目录不存在则先创建闪退日志目录 119 | mCrashInfoFolder.mkdirs(); 120 | } 121 | 122 | if (mCrashInfoFolder.exists()) { 123 | String timeStampString = DATE_FORMAT.format(new Date());//当先的时间格式化 124 | String crashLogFileName = timeStampString + ".log"; 125 | File crashLogFile = new File(mCrashInfoFolder, crashLogFileName); 126 | crashLogFile.createNewFile(); 127 | 128 | //记录闪退环境的信息 129 | RandomAccessFile randomAccessFile = new RandomAccessFile(crashLogFile, "rw"); 130 | randomAccessFile.writeChars("------------Crash Environment Info------------" + "\n"); 131 | randomAccessFile.writeChars("------------Manufacture: " + SystemUtils.getDeviceManufacture() + "------------" + "\n"); 132 | randomAccessFile.writeChars("------------DeviceName: " + SystemUtils.getDeviceName() + "------------" + "\n"); 133 | randomAccessFile.writeChars("------------SystemVersion: " + SystemUtils.getSystemVersion() + "------------" + "\n"); 134 | randomAccessFile.writeChars("------------DeviceIMEI: " + SystemUtils.getDeviceIMEI(mApplicationContext) + "------------" + "\n"); 135 | randomAccessFile.writeChars("------------AppVersion: " + SystemUtils.getAppVersion(mApplicationContext) + "------------" + "\n"); 136 | randomAccessFile.writeChars("------------Crash Environment Info------------" + "\n"); 137 | randomAccessFile.writeChars("\n"); 138 | randomAccessFile.close(); 139 | 140 | PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(crashLogFile.getAbsolutePath(), true)), true); 141 | ex.printStackTrace(pw);//写入奔溃的日志信息 142 | pw.close(); 143 | 144 | } else { 145 | Log.e(TAG, "crash info folder create failure!!!"); 146 | } 147 | 148 | } catch (IOException e) { 149 | e.printStackTrace(); 150 | } catch (Exception e) { 151 | e.printStackTrace(); 152 | } 153 | } 154 | 155 | /** 156 | * 发送发送闪退信息到远程服务器 157 | * 158 | * @param ex 159 | */ 160 | private void sendCrashInfoToServer(Throwable ex) { 161 | if (mCrashExceptionRemoteReport != null) { 162 | mCrashExceptionRemoteReport.onCrash(ex); 163 | } 164 | } 165 | 166 | /** 167 | * 闪退日志远程奔溃接口,主要考虑不同app下,把log回传给服务器的方式不一样,所以此处留一个对外开放的接口 168 | */ 169 | public static interface CrashExceptionRemoteReport { 170 | /** 171 | * 当闪退发生时,回调此接口函数 172 | * 173 | * @param ex 174 | */ 175 | public void onCrash(Throwable ex); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/clock/utils/file/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils.file; 2 | 3 | /** 4 | * 文件操作常用工具类 5 | * Created by Clock on 2015/12/31. 6 | */ 7 | public class FileUtils { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/clock/utils/text/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils.text; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | /** 7 | * 字符串通用处理类 8 | * Created by Clock on 2016/1/17. 9 | */ 10 | public class StringUtils { 11 | 12 | /** 13 | * 将字符串进行md5转换 14 | * 15 | * @param str 16 | * @return 17 | */ 18 | public static String md5(String str) { 19 | String cacheKey; 20 | try { 21 | final MessageDigest mDigest = MessageDigest.getInstance("MD5"); 22 | mDigest.update(str.getBytes()); 23 | cacheKey = bytesToHexString(mDigest.digest()); 24 | } catch (NoSuchAlgorithmException e) { 25 | cacheKey = String.valueOf(str.hashCode()); 26 | } 27 | return cacheKey; 28 | } 29 | 30 | private static String bytesToHexString(byte[] bytes) { 31 | StringBuilder sb = new StringBuilder(); 32 | for (int i = 0; i < bytes.length; i++) { 33 | String hex = Integer.toHexString(0xFF & bytes[i]); 34 | if (hex.length() == 1) { 35 | sb.append('0'); 36 | } 37 | sb.append(hex); 38 | } 39 | return sb.toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidUtils 3 | 4 | -------------------------------------------------------------------------------- /src/test/java/com/clock/utils/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.clock.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } --------------------------------------------------------------------------------