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>