Android-AsyncTask及UncaughtExceptionHandler捕獲全域性性異常(ANR、FC)
AsyncTask是Android為我們提供的方便編寫非同步任務的工具類,但是,在瞭解AsyncTask的實現原理之後,發現AsyncTask並不能滿足我們所有的需求,使用不當還有可能導致應用FC。
》AsyncTask類包含一個全域性靜態的執行緒池,執行緒池的配置引數如下:
private static final int CORE_POOL_SIZE =5;//5個核心工作執行緒
private static final int MAXIMUM_POOL_SIZE = 128;//最多128個工作執行緒
private static final int KEEP_ALIVE = 1;//空閒執行緒的超時時間為1秒
private static final BlockingQueue<Runnable> sWorkQueue =
new LinkedBlockingQueue<Runnable>(10);//等待佇列
private static final ThreadPoolExecutorsExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue,sThreadFactory);//執行緒池是靜態變數,所有的非同步任務都會放到這個執行緒池的工作執行緒內執行。
》 問題:Android的裝置一般不超過2個cpu核心,過多的執行緒會造成執行緒間切換頻繁,消耗系統資源。
。。執行緒池中已經有128個執行緒,緩衝佇列已滿,如果此時向執行緒提交任務,將會丟擲RejectedExecutionException。
問題:丟擲的錯誤不catch的話會導致程式FC。
AsyncTask可能存在新開大量執行緒消耗系統資源和導致應用FC的風險,因此,我們需要根據自己的需求自定義不同的執行緒池。
(1)、AsyncTask是封裝好的執行緒池,比起Thread+Handler的方式,AsyncTask在操作UI執行緒上更方便,因為onPreExecute()、onPostExecute()及更新UI方法onProgressUpdate()均執行在主執行緒中,這樣就不用Handler發訊息處理了;
(2)、我不太同意封裝好就會影響效能的說法,在我實際的運用中,真正的缺點來自於AsyncTask的全域性執行緒池只有5個工作執行緒,也就是說,一個APP如果運用AsyncTask技術來執行執行緒,那麼同一時間最多隻能有5個執行緒同時執行,其他執行緒將被阻塞(注:不運用AsyncTask執行的執行緒,也就是自己new出來的執行緒不受此限制),所以AsyncTask不要用於多執行緒取網路資料,因為很可能這樣會產生阻塞,從而降低效率。
》能否同時併發100+asynctask呢?
AsyncTask用的是執行緒池機制,容量是128,最多同時執行5個core執行緒,剩下的排隊。
》 導致出現Force Close的原因有很多,常見的有比如空指標啦,類沒有找到啦,資源沒找到,就連Android API使用的順序錯誤也可能導致(比如 setContentView()之前進行了findViewById()操作)
Force Close有的人說可以用來讓應用完全退出 而故意導致這個問題,讓程式強制關閉,這種做法我還是不常用。
如何避免彈出Force Close視窗 可以實現Thread.UncaughtExceptionHandler介面的uncaughtException方法 程式碼如下
importjava.lang.Thread.UncaughtExceptionHandler;
import android.app.Application;
public class MyApplication extends Application implements UncaughtExceptionHandler {
@Override
public voidonCreate() {
// TODOAuto-generated method stub
super.onCreate();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
thread.setDefaultUncaughtExceptionHandler(this);
}
}
想要哪個執行緒可以處理未捕獲異常,thread.setDefaultUncaughtExceptionHandler( this); 這句程式碼都要在那個執行緒中執行一次
》我們需要進行全域性性的異常捕獲,那麼如何捕獲全域性異常呢?
答案是 UncaughtExceptionHandler+Thread.setDefaultUncaughtExceptionHandler
1.UncaughtExceptionHandler是未捕獲異常的處理介面,該類率先捕獲異常
UncaughtExceptionHandler: 執行緒未捕獲異常控制器是用來處理未捕獲異常的。
如果程式出現了未捕獲異常預設情況下則會出現強行關閉對話方塊
實現該介面並註冊為程式中的預設未捕獲異常處理
這樣當未捕獲異常發生時,就可以做些異常處理操作
例如:收集異常資訊,傳送錯誤報告 等。
對於這個介面,我們需要進行實現
public class AppCrashHandler implements UncaughtExceptionHandler {
private Context mContext;
private Thread.UncaughtExceptionHandler mDefaultHandler;
/**防止多執行緒中的異常導致讀寫不同步問題的lock**/
private Lock lock = null;
/**本地儲存檔案日誌**/
private final String CRASH_REPORTER_EXTENSION = ".crash";
/**日誌tag**/
private final String STACK_TRACE = "logStackTrance";
/**儲存檔名**/
private final String crash_pref_path ="app_crash_pref.xml";
private AppCrashHandler()
{
lock = new ReentrantLock(true);
}
/**
* 獲得單例物件
* @param context
* @return AppCrashHandler
*/
public static AppCrashHandler shareInstance(Context context){
AppCrashHandler crashhandler = AppCrashHandler.InstanceHolder.crashHandler;
crashhandler.initCrashHandler(context);
return crashhandler;
}
/**
* 使用初始化方法初始化,防止提前初始化或者重複初始化
* @param cxt
*/
private void initCrashHandler(Context cxt)
{
if(!hasInitilized()){
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
mContext = cxt;
}
}
public interface InstanceHolder
{
public static AppCrashHandler crashHandler = new AppCrashHandler();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleExceptionMessage(ex) && mDefaultHandler != null) {
// 如果使用者沒有處理則讓系統預設的異常處理器來處理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(STACK_TRACE, "Error : ", e);
}
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
/**
* 自定義錯誤處理,收集錯誤資訊 傳送錯誤報告等操作均在此完成. 開發者可以根據自己的情況來自定義異常處理邏輯
* @param ex
* @return true:如果處理了該異常資訊;否則返回false
*/
private boolean handleExceptionMessage(Throwable ex)
{
if (ex == null)
{
return false;
}
// 使用Toast來顯示異常資訊
new Thread() {
@Override
public void run() {
// Toast 顯示需要出現在一個執行緒的訊息佇列中
Looper.prepare();
Toast.makeText(mContext, "程式出錯啦,即將退出", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
String fileName = mContext.getPackageName()+"-"+"appCrash-Exception"+ CRASH_REPORTER_EXTENSION;
String crashFileName = saveExceptionToFile(ex,fileName);
SharedPreferences.Editor editor = mContext.getSharedPreferences(crash_pref_path , Context.MODE_PRIVATE).edit();
editor.putString(STACK_TRACE, crashFileName);
editor.commit();
Log.d(STACK_TRACE, "errorLogPath="+crashFileName);
return true;
}
/**
* 是否已初始化
* @return
*/
public boolean hasInitilized()
{
return mContext!=null;
}
/**
* 儲存錯誤資訊到檔案中
* @param ex
* @return
* @throws IOException
*/
private String saveExceptionToFile(Throwable ex,String fileName)
{
File saveFile = null;
PrintWriter printWriter = null;
try {
lock.tryLock();
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
File sdCardDir = Environment.getExternalStorageDirectory();//獲取SDCard目錄
saveFile = new File(sdCardDir, fileName);
}else{
saveFile =new File(mContext.getFilesDir(),fileName);
}
if(!saveFile.exists())
{
saveFile.createNewFile();
}
printWriter = new PrintWriter(saveFile);
String result = formatException(ex);
printWriter.write(result);
printWriter.flush();
Log.e("CrashException", result);
}catch(Exception e){
e.printStackTrace();
} finally{
if(printWriter!=null)
{
printWriter.close();
}
lock.unlock();
}
return saveFile!=null?saveFile.getAbsolutePath():null;
}
/**
* 格式化異常資訊
* @param e
* @return
*/
@SuppressLint("SimpleDateFormat")
private String formatException(Throwable e)
{
StringBuilder sb = new StringBuilder();
StackTraceElement[] stackTrace = e.getStackTrace();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (stackTrace!=null)
{
String timeStramp = sdf.format(new Date(System.currentTimeMillis()));
String format = String.format("DateTime:%s\nExceptionName:%s\n\n",timeStramp,e.getLocalizedMessage());
sb.append(format);
for (int i = 0; i < stackTrace.length; i++)
{
StackTraceElement traceElement = stackTrace[i];
String fileName = traceElement.getFileName();
int lineNumber = traceElement.getLineNumber();
String methodName = traceElement.getMethodName();
String className = traceElement.getClassName();
sb.append(String.format("%s\t%s[%d].%s \n",className,fileName,lineNumber,methodName));
}
sb.append(String.format("\n%s",e.getMessage()));
Writer stringWriter = new StringWriter();
PrintWriter pw = new PrintWriter(stringWriter);
e.printStackTrace(pw);
pw.flush();
pw.close();
sb.append("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
sb.append(stringWriter.toString());
}
return sb.toString();
}
}
這裡只儲存了檔案,一般來說,當app第二次啟動時我們需要將該檔案上傳到網路,時間不是很充裕,這裡上傳暫時不貼程式碼了,時間充裕的話會及時補充,請保持關注吧
2.初始化,監聽全域性異常資訊,這裡需要繼承Application,並替換系統預設的Application
public class BaseApplication extends Application
{
private static BaseApplication instance = null;
private AppCrashHandler appCrashHandler = null;
@Override
public void onCreate() {
synchronized (this)
{
if(instance==null)
{
instance = this;
}
appCrashHandler = AppCrashHandler.shareInstance(instance);
}
super.onCreate();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
}
修改清單檔案
<application
android:name="com.hali.luya.unitest.BaseApplication "
android:hardwareAccelerated="true"
android:icon="@drawable/app_logo"
android:logo="@drawable/app_logo"
android:label="@string/app_name"
android:configChanges="locale|keyboard|screenSize"
android:theme="@style/Theme.AppBaseTheme" >
<!--- ..這裡省略一大堆程式碼.. ---->
</Application>
相關文章
- wpf 捕獲全域性異常
- SpringBoot之全域性捕獲異常Spring Boot
- android 異常捕獲-UncaughtExceptionHandlerAndroidException
- Feign失敗重試與全域性異常捕獲
- 如何自定義一個全域性異常捕獲器-SpiderManIDE
- (系列六).net8 全域性異常捕獲機制
- Java捕獲非檢查異常----UncaughtExceptionHandler的使用JavaException
- 異常及捕獲
- springboot 配置錯誤頁面及全域性異常Spring Boot
- springboot全域性異常處理Spring Boot
- .netcore全域性異常處理NetCore
- Spring-全域性異常攔截Spring
- NETCORE - 全域性異常處理(Exception)NetCoreException
- SpringBoot之全域性異常處理Spring Boot
- 【SpringBoot】全域性異常處理@ControllerAdviceSpring BootController
- springboot 全域性異常攔截器,友好異常提示Spring Boot
- 捕獲 React 異常React
- python異常捕獲Python
- C# winform NLog AOP 記錄全域性未捕獲的異常到日誌C#ORM
- 前端JavaScript 常見的報錯及異常捕獲前端JavaScript
- dotNet8 全域性異常處理
- .NetCore——全域性異常過濾器ExceptionFilterAttributeNetCore過濾器ExceptionFilter
- SpringBoot中的全域性異常處理Spring Boot
- SpringBoot處理全域性統一異常Spring Boot
- Spring Cloud Gateway的全域性異常處理SpringCloudGateway
- SpringBoot優雅的全域性異常處理Spring Boot
- python中如何捕獲異常Python
- pb呼叫ole異常捕獲
- 記錄Javascript 異常捕獲JavaScript
- Task異常捕獲的方式
- 通過自定義handler實現Thread.UncaughtExceptionHandler,實現全域性捕獲異常,或者第二種方式可以通過騰訊bugly自動整合,可在bugly文件中心檢視使用指南threadException
- Spring Boot 2 Webflux的全域性異常處理Spring BootWebUX
- 簡單的全域性異常統一處理
- spring-boot-route(四)全域性異常處理Springboot
- 設計一個全域性異常處理器
- @ControllerAdvice 全域性異常響應頁面和 JSONControllerJSON
- springboot下新增全域性異常處理和自定義異常處理Spring Boot
- 10. 異常捕獲、生成式
- Auth 授權的異常捕獲