編輯:關於Android編程
Android應用出現crash時,會出現“程序異常退出”的提示,隨後應用關閉,用戶體驗非常不好。一般為了捕獲應用運行時異常後做出適當處理,給出合理提示時,我們開發中可以繼承UncaughtExceptionHandler類來處理,提升用戶體驗。
(1)定義CrashHandler處理類,如果發生異常,則在本地文件中記錄堆棧信息,代碼如下所示:
package com.broadengate.cloudcentral.util; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Looper; import android.util.Log; import android.widget.Toast; import com.broadengate.cloudcentral.constant.Global; /** * ** <功能詳細描述> * * @author cyf * @version [版本號, 2014-6-17] * @see [相關類/方法] * @since [產品/模塊版本] */ public class CrashHandler implements UncaughtExceptionHandler { public static final String TAG = "CrashHandler"; //CrashHandler 實例 private static CrashHandler INSTANCE = new CrashHandler(); //context private Context mContext; private Thread.UncaughtExceptionHandler mDefaultHandler; private Map infos = new HashMap (); private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); private CrashHandler() { }; public static CrashHandler getInstance() { return INSTANCE; } public void init(Context context) { mContext = context; // 獲取系統默認的 UncaughtException 處理器 mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); // 設置該 CrashHandler 為程序的默認處理器 Thread.setDefaultUncaughtExceptionHandler(this); } /** * 當 UncaughtException 發生時會轉入該函數來處理 */ @Override public void uncaughtException(Thread thread, Throwable ex) { if (!handleException(ex) && mDefaultHandler != null) { // 如果用戶沒有處理則讓系統默認的異常處理器來處理 mDefaultHandler.uncaughtException(thread, ex); } else { try { thread.sleep(4000); } catch (InterruptedException e) { Log.e(TAG, "error:", e); } // 退出程序,注釋下面的重啟啟動程序代碼 Global.logoutApplication(mContext); } } /** * * <自定義錯誤處理,收集錯誤信息,發送錯誤報告等操作均在此完成> * <功能詳細描述> * @param ex * @return * @see [類、類#方法、類#成員] */ private boolean handleException(Throwable ex) { if (ex == null) { return false; } //使用Toast 來顯示異常信息 new Thread() { @Override public void run() { Looper.prepare(); Toast.makeText(mContext, "很抱歉,程序出現異常,即將退出", Toast.LENGTH_LONG).show(); Looper.loop(); } }.start(); collectDeviceInfo(mContext); saveCrashInfo2File(ex); return true; } /** * * <手機設備參數信息-收集設備信息> * <功能詳細描述> * @param ctx * @see [類、類#方法、類#成員] */ public void collectDeviceInfo(Context ctx) { try { PackageManager pm = ctx.getPackageManager(); PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); if (pi != null) { String versionName = pi.versionName == null ? "null" : pi.versionName; String versionCode = pi.versionCode + ""; infos.put("versionName", versionName); infos.put("versionCode", versionCode); } } catch (NameNotFoundException e) { Log.e(TAG, "an error occured when collect package info", e); } Field[] fields = Build.class.getDeclaredFields(); for (Field field : fields) { try { field.setAccessible(true); infos.put(field.getName(), field.get(null).toString()); Log.d(TAG, field.getName() + ":" + field.get(null)); } catch (Exception e) { Log.e(TAG, "an error occured when collect crash info", e); } } } /** * * <保存錯誤信息到文件中> * <功能詳細描述> * @param ex * @return * @see [類、類#方法、類#成員] */ private void saveCrashInfo2File(Throwable ex) { StringBuffer sb = new StringBuffer(); for (Map.Entry entry : infos.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key + "=" + value + "\n"); } Writer writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); ex.printStackTrace(printWriter); Throwable cause = ex.getCause(); while (cause != null) { cause.printStackTrace(printWriter); cause = cause.getCause(); } printWriter.close(); String result = writer.toString(); sb.append(result); try { String time = formatter.format(new Date()); String fileName = FileSystemManager.getCrashPath(mContext) + time + ".log"; FileOutputStream fos = new FileOutputStream(fileName); fos.write(sb.toString().getBytes()); fos.close(); } catch (Exception e) { Log.e(TAG, "an error occured while writing file...", e); } } }
可以看出String fileName = FileSystemManager.getCrashPath(mContext) + time + “.log”;文件名用時間戳作為關鍵字段,並保存在本地,等待上傳。
收集錯誤信息,發送錯誤報告等操作均在handleException中完成,通過上面的代碼可以看到,一旦應用發生崩潰,就會自動調用uncaughtException方法,然後調用handleException方法完成收集手機信息,保存崩潰堆棧至本地,並且清理當前應用activity堆棧,最後自動關閉應用,並給出toast提示。
(2)Application中配置是否打開全局補獲異常
package com.broadengate.cloudcentral;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import cn.jpush.android.api.JPushInterface;
import com.broadengate.cloudcentral.constant.Global;
import com.broadengate.cloudcentral.sharepref.SharePref;
import com.broadengate.cloudcentral.util.CMLog;
import com.broadengate.cloudcentral.util.CrashHandler;
import com.broadengate.cloudcentral.util.FileSystemManager;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer;
/**
*
* <應用初始化> <功能詳細描述>
*
* @author cyf
* @version [版本號, 2014-3-24]
* @see [相關類/方法]
* @since [產品/模塊版本]
*/
public class CCApplication extends Application
{
/**
* app實例
*/
public static CCApplication ccApplication = null;
/**
* 本地activity棧
*/
public static List activitys = new ArrayList();
/**
* 加解密密鑰
*/
public static String key = "";
@Override
public void onCreate()
{
super.onCreate();
ccApplication = this;
new SharePref(ccApplication).saveAppOpenOrClose(true);
loadData(getApplicationContext());
JPushInterface.setDebugMode(false); // 設置開啟日志,發布時請關閉日志
JPushInterface.init(this); // 初始化 JPush
CrashHandler crashHandler = CrashHandler.getInstance();//打開全局異常捕獲
crashHandler.init(getApplicationContext());
}
public static void loadData(Context context)
{
// This configuration tuning is custom. You can tune every option, you may tune some of them,
// or you can create default configuration by
// ImageLoaderConfiguration.createDefault(this);
// method.
ImageLoaderConfiguration config =
new ImageLoaderConfiguration.Builder(context).threadPriority(Thread.NORM_PRIORITY - 2)
.threadPoolSize(4)
.tasksProcessingOrder(QueueProcessingType.FIFO)
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new LruMemoryCache(4 * 1024 * 1024))
.discCacheSize(50 * 1024 * 1024)
.denyCacheImageMultipleSizesInMemory()
.discCacheFileNameGenerator(new Md5FileNameGenerator())
.tasksProcessingOrder(QueueProcessingType.LIFO)
.discCache(new UnlimitedDiscCache(new File(FileSystemManager.getCacheImgFilePath(context))))
.build();
ImageLoader.getInstance().init(config);
}
@Override
public void onTerminate()
{
super.onTerminate();
try
{
CCApplication.key = "";
Global.setUserId("");
Global.setStore("");
Global.setLogin(false);
//保存到配置文件
SharePref preference = new SharePref(ccApplication);
preference.saveInitFlag(false);
preference.saveIsLogin(false);
preference.saveAppOpenOrClose(false);
for (Activity activity : activitys)
{
activity.finish();
}
}
catch (Exception e)
{
CMLog.e("", "finish activity exception:" + e.getMessage());
}
finally
{
ImageLoader.getInstance().clearMemoryCache();
System.exit(0);
}
}
/**
*
* <添加> <功能詳細描述>
*
* @param activity
* @see [類、類#方法、類#成員]
*/
public void addActivity(Activity activity)
{
activitys.add(activity);
}
}
(3)下一次啟動後,上傳上次保存在本地的奔潰文件,上傳成功後,刪除,避免重復上傳。
/**
*
* <上傳崩潰日志文件到服務器>
* <功能詳細描述>
* @see [類、類#方法、類#成員]
*/
private void uploadCrashFile()
{
File root = new File(FileSystemManager.getCrashPath(HomeFragmentActivity.this));
File[] listFiles = root.listFiles();
if (listFiles.length > 0)
{
ArrayList files = new ArrayList();
for (File file : listFiles)
{
files.add(file);
}
Map> fileParameters = new HashMap>();
fileParameters.put("file", files);
ConnectService.instance().connectServiceUploadCrashFile(HomeFragmentActivity.this,
null,
fileParameters,
HomeFragmentActivity.this,
UploadCrashFileResponse.class,
URLUtil.UPLOAD_CRASH_FILE);
}
}
上傳成功後的代碼
/**
* 向服務器上傳崩潰文件
*/
else if (ob instanceof UploadCrashFileResponse)
{
UploadCrashFileResponse uploadCrashFileResponse = (UploadCrashFileResponse)ob;
if (GeneralUtils.isNotNullOrZeroLenght(uploadCrashFileResponse.getRetcode()))
{
if (Constants.SUCESS_CODE.equals(uploadCrashFileResponse.getRetcode()))
{
//發送成功後刪除本地文件
FileUtil.deleteDirectory(FileSystemManager.getCrashPath(HomeFragmentActivity.this));
}
}
}
(4)文件夾公共類
package com.broadengate.cloudcentral.util;
import java.io.File;
import android.content.Context;
/**
*
* <管理本地文件目錄>
* <功能詳細描述>
*
* @author cyf
* @version [版本號, 2014-6-30]
* @see [相關類/方法]
* @since [產品/模塊版本]
*/
public class FileSystemManager
{
/**
* 根目錄緩存目錄
*/
private static String cacheFilePath;
/**
* 列表頁面圖片緩存目錄
*/
private static String cacheImgFilePath;
/**
* 用戶頭像緩存目錄
*/
private static String userHeadPath;
/**
* 省市區數據庫緩存目錄
*/
private static String dbPath;
/**
* 投訴維權圖片緩存目錄
*/
private static String mallComplaintsPicPath;
/**
* 投訴維權語音緩存目錄
*/
private static String mallComplaintsVoicePath;
/**
* 崩潰日志緩存目錄(上傳成功則刪除)
*/
private static String crashPath;
/**
* 圈子發帖緩存目錄(刪除上次發帖緩存,保存本次發帖紀錄)
*/
private static String postPath;
/**
* 臨時目錄
*/
private static String temporaryPath;
/**
*
* <根目錄緩存目錄>
* <功能詳細描述>
* @param context
* @return
* @see [類、類#方法、類#成員]
*/
public static String getCacheFilePath(Context context)
{
cacheFilePath = FileUtil.getSDPath(context) + File.separator + "cloudcentral" + File.separator;
return cacheFilePath;
}
/**
*
* <列表頁面圖片緩存目錄>
* <功能詳細描述>
* @param context
* @return
* @see [類、類#方法、類#成員]
*/
public static String getCacheImgFilePath(Context context)
{
String path = getCacheFilePath(context) + "img" + File.separator;
cacheImgFilePath = FileUtil.createNewFile(path);
FileUtil.createNewFile(path + ".nomedia");
return cacheImgFilePath;
}
/**
*
* <用戶頭像緩存目錄>
* <功能詳細描述>
* @param context
* @param userId
* @return
* @see [類、類#方法、類#成員]
*/
public static String getUserHeadPath(Context context, String userId)
{
userHeadPath =
FileUtil.createNewFile(getCacheFilePath(context) + "head" + File.separator + userId + File.separator);
return userHeadPath;
}
/**
*
* <省市區數據庫緩存目錄>
* <功能詳細描述>
* @param context
* @return
* @see [類、類#方法、類#成員]
*/
public static String getDbPath(Context context)
{
dbPath = FileUtil.createNewFile(getCacheFilePath(context) + "database" + File.separator);
return dbPath;
}
/**
*
* <投訴維權圖片緩存目錄>
* <功能詳細描述>
* @param context
* @param userId
* @return
* @see [類、類#方法、類#成員]
*/
public static String getMallComplaintsPicPath(Context context, String userId)
{
mallComplaintsPicPath =
FileUtil.createNewFile(getCacheFilePath(context) + "mallcomplaints" + File.separator + userId
+ File.separator + "pic" + File.separator);
return mallComplaintsPicPath;
}
/**
*
* <投訴維權語音緩存目錄>
* <功能詳細描述>
* @param context
* @param userId
* @return
* @see [類、類#方法、類#成員]
*/
public static String getMallComplaintsVoicePath(Context context, String userId)
{
mallComplaintsVoicePath =
FileUtil.createNewFile(getCacheFilePath(context) + "mallcomplaints" + File.separator + userId
+ File.separator + "voice" + File.separator);
return mallComplaintsVoicePath;
}
/**
*
* <崩潰日志緩存目錄(上傳成功則刪除)>
* <功能詳細描述>
* @param context
* @return
* @see [類、類#方法、類#成員]
*/
public static String getCrashPath(Context context)
{
crashPath = FileUtil.createNewFile(getCacheFilePath(context) + "crash" + File.separator);
return crashPath;
}
/**
*
* <圈子發帖緩存目錄(刪除上次發帖緩存,保存本次發帖紀錄)>
* <功能詳細描述>
* @param context
* @return
* @see [類、類#方法、類#成員]
*/
public static String getPostPath(Context context)
{
postPath = FileUtil.createNewFile(getCacheFilePath(context) + "post" + File.separator);
return postPath;
}
/**
*
* <臨時圖片緩存目錄>
* <功能詳細描述>
* @param context
* @return
* @see [類、類#方法、類#成員]
*/
public static String getTemporaryPath(Context context)
{
temporaryPath = FileUtil.createNewFile(getCacheFilePath(context) + "temp" + File.separator);
return temporaryPath;
}
}
package com.broadengate.cloudcentral.util;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.http.util.EncodingUtils;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.os.Environment;
import android.os.Parcelable;
public class FileUtil
{
// 獲取sdcard的目錄
public static String getSDPath(Context context)
{
// 判斷sdcard是否存在
if (Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
{
// 獲取根目錄
File sdDir = Environment.getExternalStorageDirectory();
return sdDir.getPath();
}
return "/data/data/" + context.getPackageName();
}
public static String createNewFile(String path)
{
File dir = new File(path);
if (!dir.exists())
{
dir.mkdirs();
}
return path;
}
// 復制文件
public static void copyFile(InputStream inputStream, File targetFile)
throws IOException
{
BufferedOutputStream outBuff = null;
try
{
// 新建文件輸出流並對它進行緩沖
outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));
// 緩沖數組
byte[] b = new byte[1024 * 5];
int len;
while ((len = inputStream.read(b)) != -1)
{
outBuff.write(b, 0, len);
}
// 刷新此緩沖的輸出流
outBuff.flush();
}
finally
{
// 關閉流
if (inputStream != null)
inputStream.close();
if (outBuff != null)
outBuff.close();
}
}
/**
* 文件是否已存在
*
* @param file
* @return
*/
public static boolean isFileExit(File file)
{
if (file.exists())
{
return true;
}
return false;
}
/**
* 判斷指定目錄是否有文件存在
*
* @param path
* @param fileName
* @return
*/
public static File getFiles(String path, String fileName)
{
File f = new File(path);
File[] files = f.listFiles();
if (files == null)
{
return null;
}
if (null != fileName && !"".equals(fileName))
{
for (int i = 0; i < files.length; i++)
{
File file = files[i];
if (fileName.equals(file.getName()))
{
return file;
}
}
}
return null;
}
/**
* 根據文件路徑獲取文件名
*
* @return
*/
public static String getFileName(String path)
{
if (path != null && !"".equals(path.trim()))
{
return path.substring(path.lastIndexOf("/"));
}
return "";
}
// 從asset中讀取文件
public static String getFromAssets(Context context, String fileName)
{
String result = "";
try
{
InputStreamReader inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName));
BufferedReader bufReader = new BufferedReader(inputReader);
String line = "";
while ((line = bufReader.readLine()) != null)
result += line;
return result;
}
catch (Exception e)
{
e.printStackTrace();
}
return result;
}
public static String FileInputStreamDemo(String path)
{
try
{
File file = new File(path);
if (!file.exists() || file.isDirectory())
;
FileInputStream fis = new FileInputStream(file);
byte[] buf = new byte[1024];
StringBuffer sb = new StringBuffer();
while ((fis.read(buf)) != -1)
{
sb.append(new String(buf));
buf = new byte[1024];// 重新生成,避免和上次讀取的數據重復
}
return sb.toString();
}
catch (Exception e)
{
}
return "";
}
public static String FileInputStreamDemo(String fileName, Context context)
{
try
{
AssetManager aManager = context.getResources().getAssets();
InputStream in = aManager.open(fileName); // 從Assets中的文件獲取輸入流
int length = in.available(); // 獲取文件的字節數
byte[] buffer = new byte[length]; // 創建byte數組
in.read(buffer); // 將文件中的數據讀取到byte數組中
String result = EncodingUtils.getString(buffer, "UTF-8");
return result;
}
catch (Exception e)
{
// Log.e("", "error:" + e.getMessage());
}
return "";
}
/**
* 刪除目錄(文件夾)下的文件
*
* @param sPath
* 被刪除目錄的文件路徑
* @return 目錄刪除成功返回true,否則返回false
*/
public static void deleteDirectory(String path)
{
File dirFile = new File(path);
File[] files = dirFile.listFiles();
if (files != null && files.length > 0)
{
for (int i = 0; i < files.length; i++)
{
// 刪除子文件
if (files[i].isFile())
{
files[i].delete();
}
// 刪除子目錄
else
{
deleteDirectory(files[i].getAbsolutePath());
}
}
}
}
// 保存序列化的對象到app目錄
public static void saveSeriObj(Context context, String fileName, Object o)
throws Exception
{
String path = context.getFilesDir() + "/";
File dir = new File(path);
dir.mkdirs();
File f = new File(dir, fileName);
if (f.exists())
{
f.delete();
}
FileOutputStream os = new FileOutputStream(f);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(os);
objectOutputStream.writeObject(o);
objectOutputStream.close();
os.close();
}
// 讀取序列化的對象
public static Object readSeriObject(Context context, String fileName)
throws Exception
{
String path = context.getFilesDir() + "/";
File dir = new File(path);
dir.mkdirs();
File file = new File(dir, fileName);
InputStream is = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(is);
Object o = objectInputStream.readObject();
return o;
}
/**
* 保存camera capture到file
* @return 是否成功
*/
public static boolean savePhoto(Parcelable data, String path)
{
boolean rs = false;
// Bitmap photo = new Intent().getExtras().getParcelable("data");
if (null == data)
{
return rs;
}
try
{
Bitmap photo = (Bitmap)data;
CMLog.w("FileUtil.save", "orign size:"+ photo.getByteCount());
File file = new File(path);
file.createNewFile();
FileOutputStream out = new FileOutputStream(file);
// photo.compress(Bitmap.CompressFormat.PNG, 100, out);
// ByteArrayOutputStream out = new ByteArrayOutputStream();
//TODO 調用BimmapUtil壓縮
rs = photo.compress(Bitmap.CompressFormat.JPEG, 100, out);//PNG
out.flush();
out.close();
rs &= true;
CMLog.w("FileUtil.save", "file size:"+ file.length());
}
catch (FileNotFoundException e)
{
e.printStackTrace();
rs = false;
}
catch (IOException e)
{
e.printStackTrace();
rs = false;
}
return rs;
}
/**
* 下載音頻文件,先從本地獲得,本地沒有 再從網絡獲得
* <一句話功能簡述>
* <功能詳細描述>
* @param url
* @param context
* @param userId
* @return
* @see [類、類#方法、類#成員]
*/
public static String getAudiaFile(String url, Context context, String userId)
{
String audioName = url.substring(url.lastIndexOf("/") + 1, url.length());
File file = new File(FileSystemManager.getMallComplaintsVoicePath(context, userId), audioName);
if (file.exists())
{
return file.getPath();
}
return loadImageFromUrl(context,url,file);
}
public static String loadImageFromUrl(Context context,String imageURL, File file)
{
try
{
URL url = new URL(imageURL);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setConnectTimeout(5*1000);
con.setDoInput(true);
con.connect();
if (con.getResponseCode() == 200)
{
InputStream inputStream = con.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024*20];
int length = -1;
while((length = inputStream.read(buffer)) != -1)
{
byteArrayOutputStream.write(buffer,0,length);
}
byteArrayOutputStream.close();
inputStream.close();
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(byteArrayOutputStream.toByteArray());
outputStream.close();
return file.getPath();
}
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
return "";
}
}
分析Android多主題顏色的相關問題
如果您通過以下的代碼來獲取定義的顏色值context.getResources().getColor(R.color.some_color_resource_id);在
Android系統的啟動過程
當我們拿到一台Android的智能手機,從打開開關,到我們可以使用其中的app時,這個啟動過程到底是怎麼樣的? 系統上電 當給Android系統上電,CPU復位之後,
Android WebView 的簡單使用
Android WebView 1.首先修改activity.xml中的代碼:2.然後MainActivity中的代碼:3.最後設置權限:<uses-permiss
Android ActionBar完全解析,使用官方推薦的最佳導航欄(下)
限於篇幅的原因,在上篇文章中我們只學習了ActionBar基礎部分的知識,那麼本篇文章我們將接著上一章的內容繼續學習,探究一下ActionBar更加高級的知