Android异常整理——《App研发录—架构设计,Crash分析和竞品技术分析》

常见的异常

Java语法相关的异常

  • 空指针:NullPointException

    1.方法需要对传入的参数判空后再使用
    2.对外部接口的调用,需要确保返回值中不为空
    3.在App中过多使用全局变量,一旦发生内存回收,全局变量会被置为空,可以将全局变量序列化到本地,为空时从本地反序列化回来
    
  • 角标越界:IndexOutOfBoundsException,StringIndexOutOfBoundsException,ArrayIndexOutOfBoundsException

    1.在遍历一个数组/集合时,要预判数组/集合是否为空,长度是否大于0
    2.在使用数组/集合中的元素时,要预判数组/集合长度是否有这么长
    3.先判断字符串长度再使用,subString(start,end)这样的函数
    
  • 试图调用一个空对象的方法:Attempt to invoke virtual method on a null object reference

  • 类型转换异常:ClassCastException:classA cannot be cast to classB

    1.强制类型转换导致,可以使用安全类型转换函数,如convertToInt(obj, 0)
    
    public final static int convertToInt(Object value, int defaultValue) { 
        if (value == null || "".equals(value.toString().trim())) {    
            return defaultValue;    
        }    
        try {
            return Integer.valueOf(value.toString()); 
        } catch (Exception e) {     
            try {           
                return Double.valueOf(value.toString()).intValue(); 
            } catch (Exception e1) {
                return defaultValue;        
            }   
        }
    }
    
  • 数字转换错误:NumberFormatException

    如:
    String abc = "1122xx";
    int result = Integer.parseInt(abc);
    
  • 声明数组时长度为-1:NegativeArraySizeException

    如:
    String[] arg1 = new String[args.length - 1];//args.length为0
    
  • 遍历集合同时删除其中元素:ConcurrentModificationException

  • 比较器使用不当:Comparison method violates its general contract!

    如:
    Comparator comparator = new Comparator(){
        public int compare(Double d1,Double d2){
            return p1 > p2 ? 1 : -1;
        }
    };//没有考虑到p1==p2的情况
    
  • 当除数为0:java.lang.ArithmeticException:divide by zero

  • 不能随便使用的asList:

    java.lang.UnsupportedOperationException at java.util.AbstractList.remove(AbstractList.java:144)
    at java.util.AbstractList$Itr.remove(AbstractList.java:360) at
    java.util.AbstractCollection.remove(AbstractCollection.java:252) at

    如:
    String str = "1,2,3";
    List test = Arrays.asList(str.split(","));//asList()返的是Arrays$ArrayList,其没有实现add和remove方法
    test.remove("1");
    解决方法:将Arrays$ArrayList转换为ArrayList
    String str = "1,2,3";
    List test = Arrays.asList(str.split(","));//asList()返的是Arrays$ArrayList,其没有实现add和remove方法
    List arrayList = new ArrayList(test);
    arrayList.remove("1");
    
  • 类找不到:ClassNotFoundException

    动态加载一个类时,如果这个类在运行时找不到,就会抛出这个异常
    
  • 类找不到:NoClassDefFoundError

    当在一个B来中声明A类的实例
    ClassA obj = new ClassA();
    但是打包时B和A在不同的dex中,如果在A所在的dex中把A类删除了,就会抛出此异常
    

Activity相关的异常

  • 找不到Activity:android.content.ActivityNotFoundException:No Activity found to handle Intent {…}

    错误原因:URL不是以http开头,代码就会抛出异常
    Uri uri = Uri.parse("www.baidu.com");
    Intent intent = new Intent(Intent.ACTION_VIEW,uri);
    startActivity(intent);
    
  • 不能实例化Activity:java.lang.RuntimeException:Unable to instantiate activity ComponentInfo

    没有在清单文件中注册
    
  • 找不到Service:java.lang.RuntimeException:Unable to instantiate receiver

    注意检查代码中是否有Class.forName("class1")这样的语句,ProGuard会将class1混淆,从而就找不到class1这个类
    
  • 不能启动BroadcastReceiver:Unable to start receiver

    使用Activity以外的content来startActivity,必须指定为Intent.FLAG_ACTIVITY_NEW_TASK
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
  • startActivityForResult不能回传:Failure delivering result ResultInfo{who=null,request=0,result=-1}

    传回来的key是A,但是却按照B这个key来取值
    
  • Fragment not attached to Activity

    Fragment还没有Attach到Activity时就调用了诸如getResourse()这样的方法
    解决方案:先使用isAdded方法进行判断
    if(isAdded()){//isAdded方法是Android系统提供的,只有在Fragment被添加到所属的Activity后才返回true
        getResourses().getString(...);
    }
    

序列化相关的异常

  • 实体对象不支持序列化:Parcelable encountered IOException writing serializable object(name=xxx)…

  • 序列化时未指定ClassLoader:BadParcelableException:ClassNotFoundException when unmarshalling…

    public class MyParcelable implements Parcelable {
        private String mStr;
        private ClassA a;
    
        ...
    
        private MyParcelable(Parcel in) {
            mStr = in.readString();
            a = in.readParcelable(null);
        }
    } 
    

    崩溃处在最后一句,对a的反序列化上,改成

    a = in.readParcelable(ClassA.class.getClassLoader());
    
  • 反序列化时发现类找不到(被ProGuard混淆导致的崩溃):Parcelable encountered ClassNotFoundException reading a Serializable object …

    -keep class * implements java.io.Serializable
    
    -keepclassmembers class * implements java.io.Serializable {
        static final long serialVersionUID;
        private static final java.io.ObjectStreamField[] serialPersistentFields;
        !static !transient ;
        !private ;
        !private ;
        private void writeObject(java.io.ObjectOutputStream);
        private void readObject(java.io.ObjectInputStream);
        java.lang.Object writeReplace();
        java.lang.Object readResolve();
    }
    
  • 反序列化时发现类找不到(传入畸形数据):parcelable encountered ClassNotFoundException reading a Serializable object(name=某个类的名称)

  • 反序列化时出错:Could not read input channel file descriptors from parcel…

    可能是intent传递的数据太大导致的
    

列表相关的异常

  • Adapter数据源变化但是没通知ListView:The content of the adapter has changed but ListView did not receive a notification…

  • ListView滚动时点击刷新按钮后崩溃:

    java.lang.IndexOutOfBoundsException:Invalid index 30,size is 1 at
    java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251)…

    解决方案:ListView滚动的时候将刷新按钮设置为不可点击
    
  • AbsListView的obtainView返回空指针:java.lang.NullPointerException at android.widget.AbsListView.obtain View(AbsListView.java:1521)at android.widget.ListView.makeAndAddView

    导致的原因是getView方法在某些时候返回null,返回的时候需判空,如果为null就返回convertView
    
  • Adapter数据源变化但是没调用notifyDataSetChanged:The application’s PagerAdapter changed the adapter’s contents without calling PagerAdapter#notifyDataSetChanged

    PagerAdapter对于notifyDataSetChanged()和getCount()的执行顺序是非常严格的,系统跟踪count的值,如果这个值和getCount返回的值不一致,就会抛出此异常
    

窗体相关的异常

  • 窗体句柄泄露:

    android.view.WindowLeaded:Activity xxx has leaked window
    com.android.internal.policy.impl.Phone.Window$DecorView{xxxx} that was originally added here.

    可能是在非主线程中的某些操作不当而产生了一个严重的异常,从而强制关闭当前Activity,而在关闭的同时,没能及时调用dismiss来解除ProgressDialog等的引用,从而系统抛出上述崩溃信息
    解决方法:重写Activity的onDestroy方法,在方法中调用dismiss来解除对ProgressDialog等的引用
    
  • View not attached to window manager:

    java.lang.IllegalArgumentException:View not attached to window manager at
    android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:356) at
    android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:201) at…

    发生这类Exception的场景是,有一个耗时的线程任务,在任务开始的时候显示一个对话框,然后当任务完成后再销毁,在此期间Activity因为某种原因被杀掉且又重新启动了,那么当Dialog调用dismiss方法的时候就会抛此异常
    
  • token null is not for an application:android.view.WindowManager$BadTokenException:Unable to add window – token null is not for an application

    new AlertDialog.Builder(getApplicationContext())
        .setIcon(...)
            ...
        .show();
    
    只有Activity才能添加一个窗体
    
  • permission denied for this window type:Android.view.WindowManager$BadTokenException:Unable to add window android.view.ViewRootImpl$W@411da608 – permission denied for this window type

    多发生在使用WindowManager自定义弹出框时,没有设置权限,需添加
    
    
    
    
    
    
    
  • is your activity running:

    android.view.WindowManager$BadTokenException:Unable to add window – token
    android.app.LocalActivityManager$LocalActivityRecord@45a58ee0 is not valid;is your activity running?

    由于Activity A依附于ActivityB,当Activity B产生错误的时候,Activity A因为调用了一个已经被finish()/或者还没有执行完onCreate()的Activity抛出此异常
    
  • 添加窗体失败

    java.lang.RuntimeException:Adding window failed at
    android.view.ViewRootImpl.setView(ViewRootImpl.java:511) at
    android.view.WindowManagerImpl.addView(WindowManagerImpl.java:301) at
    android.view.WindowManagerImpl.addView(WindowManagerImpl.java:215) at…

  • AlertDialog.resolveDialogTheme

    java.lang.NullPointerException at
    android.app.AlertDialog.resolveDialogTheme(AlertDialog.java:142) at
    android.app.AlertDialog$Builder.(AlertDialog.java:359) at
    com.radzik.devadmin.MainActivity$5.onClick(MainActivity.java:140) at
    android.view.View.performClick(View.java:4084)…

    1.在B页面写了一个show方法,控制AlertDialog.Builder的弹出和隐藏,在A页面调用B页面的show方法
    2.在TabActivity中切换Tab时,也容易产生这个Crash,因为在new对话框的时候参数content指定成了this,即指向当前子Activity的content,但子Activity是动态创建的,不能保证一直存在,所以将this替换为getParent()即可
    
  • The specified child already has a parent.You must call removeView() on the child’s parent first

    先将child从它的父布局中移除:parent.removeView(child);
    再设置
    
  • 子线程不能修改UI:android.view.ViewRootImpl$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views…

  • 不能在子线程操作AlertDialog和Toast:Can’t create handler inside thread that has not called Looper.prepare()

资源相关的异常

  • Resources$NotFoundException:android.content.res.Resources$NotFoundException:String resource ID #0x1

  • StackOverfiowError

    主要是因为Layout布局文件结构嵌套层次太深,尽量控制在5层以下
    
    还有可能是App中有多个线程,在退出App的时候可能不能完全关闭App,必须使用System.exit(0)
    
  • UnsatisfiedLinkError:
    java.lang.UnsatisfiedLinkError:dalvik.system.PathClassLoader [DexPathList[[zip file “/data/app/appname-1.apk”]…

    造成这个Crash肯定是so格式的文件没有加载到
    
  • InfiateException之FileNotFoundException

    Caused by:android.view.InfiateException:Binary XML file line #18:Error infiating class at
    android.view.LayoutInfiater.createView(LayoutInfiater.java:518)
    Caused by:java.io.FileNotFoundException:res/drawable-hdpi/add.png at
    android.content.res.AssetManager.openNonAssetNative(Native Method)

    可能是GC导致的,Activity销毁了,但是里面涉及的资源并没有被回收,于是便产生内存泄露,但是表现为FileNotFoundException
    解决方案是在Activity的onStop方法中,手动释放每一张图片资源:
    
        Drawable d = imageView.getDrawable();  
        if (d != null)  
            d.setCallback(null);  
        imageView.setImageDrawable(null);  
        imageView.setBackgroundDrawable(null);  
    
  • InfiateException之缺少构造器:

    android.view.InfiateException:Binary XML file line #:Error infiating class com.example.activity1.TestButton

    自定义控件若需在xml文件中使用,就必须重写带有两个参数的构造方法
    
  • InfiateException之style与android:textStyle的区别:android.view.InfiateException:Binary XML file line #14:Error infiating class

    引用定义好的样式
    
    style = "@style/NormalText"
    
  • TransactionTooLargeException:android.view.InfiateException:Binary XML file line #14:Error infiating class

    Binder最大通常限制为1MB,大于1MB就会抛出此异常
    

系统碎片化相关的异常

  • NoSuchMethodError:java.lang.NoSuchMethodError

    可能是Android不同版本的API不同导致,可以在不同版本的SDK上编译,发现错误,或者对版本进行判断
    
  • RemoteViews:android.widget.RemoteViews$RefiectionAction.writeToParcel(RemoteViews.java:763)

    当Android版本是4.1以下时,Bitmap为null会抛出此异常
    
  • pointerIndex out of range:

    java.lang.IllegalArgumentException:pointerIndex out of range at
    android.view.MotionEvent.nativeGetAxisValue(Native Method)

    在做多点触控放大缩小,操作自己绘制的图形时发生此异常,可以在绘图的时候捕获这个异常
    
  • SecurityException之一:Intent中图片太大

    Unable to find app for caller android.app.ApplicationThreadProxy@41868f10(pid=24370) when stopping service Intent {cmp=xxx}

  • SecurityException之二:动态加载其他apk的activity

    java.lang.SecurityException:Given caller package com.jianqiang.abc is not running in process ProcessRecord {41e74e50 28637:com.zhao3546.launcher/u0a10142}

    如果在apk中使用了动态注册BroadcastReceiver,那么Launcher动态加载该apk时,就有可能出现java.lang.SecurityException异常
    
    解决方法参照:http://blog.csdn.net/zhao_3546/article/details/11195881
    
  • SecurityException之三:No permission to modify thread

    java.lang.SecurityException:No permission to modify given thread at
    android.os.Process.setThreadPriority(Native Method) at
    android.webkit.WebViewCore$WebCoreThread$1.handleMessage(WebViewCore.java:764)

    在执行某些需要权限的操作时,要么加上if语句跳过这个操作,要么使用try...catch...捕获这类异常
    
  • view的getDrawingCache()返回null

    java.lang.NullPointerException at
    android.view.View.buildDrawingCache(View.java:6578) at
    android.view.View.getDrawingCache(View.java:6428) at…

    当背景图太大,超过了屏幕大小,就会导致getDrawingCache()返回的结果是null
    
  • DeadObjectException

    某个对象已经被系统回收了,可我们还在使用它
    
  • ViewFlipper引发的血案

    java.lang.IllegalArgumentException:Receiver not registered:
    android.widget.ViewFlipper$1@4083a4d0 at
    android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:634)

    在Activity中使用ViewFlipper控件,进行横竖屏切换操作时就会发生这种异常,这是由于onDetachedFromWindow()在onAttachedToWindow()之前被调用所致,需重写onDetachedFromWindow()方法
    
    @Override
    protected void onDetachedFromWindow(){
        try{
            super.onDetachedFromWindow();
        }catch(IllegalArgumentException e){
            stopFlipping();
        }
    }
    
  • ActivityNotFoundException:

    android.content.ActivityNotFoundException:Unable to find explicit activity class
    {com.android.settings/com.android.settings.WirelessSettings};
    have you declared this activity in your AndroidManifest.xml?

    Android4.0以上把原先的打开网络设置方式舍弃了
    
    if(Build.VERSION.SDK_INT > 13){
        startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS));
    }else {
        startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS));
    }
    
  • Package manager has died:

    Packeage manager has died at
    android.app.ApplicationPackageManager.getApplicationInfo(ApplicationPackageManager.java:213)

    try{
        String channelId = getPackageManager()
            .getApplicationInfo(
                getPackageName(),PackageManager.GET_META_DATA)
            .metaData.getString("UMENG_CHANNEL");
    
        PackageInfo info = this.getPackageManager()
                    .getPackageInfo(getPackageName(),0);
    }catch(PackageManager.NameNotFoundException e){
    
    }
    
    PackageManager如果已经died,说明该进程不存在了,此时任何向它进行的请求都将失效,解决方案就是每次获取PackageManager的时候用try...catch...捕获异常
    
  • SpannableString与富文本字符串:java.lang.IndexOutOfBoundsException:setSpan(-1…-1) starts before 0 at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:951) at …

    可能是在长按一段文本时,有些Android系统对于EditText的getSelectionStart方法,会返回-1
    
  • Can not perform this action after onSaveInstanceState

    java.lang.IllegalStateException:
    Can not perform this action after onSaveInstanceState at
    android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1314)…
    android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:595)

    commit()方法在Activity的onSavaInstanceState()之后调用就会出错,因为onSaveInstanceState方法是在Activity即将被销毁前调用,以保存Activity数据的,如果在保存完状态后再添加Fragment就会出错
    解决方法是把commit()方法替换成commitAllowingStateLoss()
    
  • Service Intent must be explicit

    Android在升级到5.0系统后会产生这样的崩溃,直接通过action启动Service就会导致这个问题,必须指定component或package
    
    Intent intent= new Intent();
    intent.setAction("your action name");
    intent.setPackage(getPackageName());
    context.startService(intent);
    

SQLite相关的异常

  • No transaction is active:android.database.sqlite.SQLiteException:cannot commit - no transaction is active

    在事务中,逐条循环插入大量数据时会导致此崩溃,解决方法是一次性地把这些数据都插入到数据库中
    
    public void insertOrUpdateDataBatch(){
        SQLiteDatabase db = getWritableDatabase();
        db.beginTransaction();
        try{
            for(String sql : sqls){
                db.execSQL(sql);
            }
            //设置事务标志为成功,当结束事务时就会提交事务
            db.setTransactionSuccessful();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            db.endTransaction();
            db.close();
        }
    }
    
  • 忘记关闭Cursor:android.database.CursorWindowAllocationException:Cursor window allocation of 2048kb failed

  • 数据库被锁定:android.database.sqlite.SQLiteDatabaseLockedException:database is locked

    在不同的线程中创建多个连接时,就会抛出此异常,解决方案是将数据库做成一个单例
    
  • 试图再打开已经关闭的对象:java.lang.IllegalStateException:attempt to re-open an already-closed object

    多线程操作数据,A读完数据库将其关闭,B此时正在写数据,就会发生此Crash
    在聊天室中,保持数据库一直处于Open状态,等退出聊天室再执行close方法
    
  • 文件加密了或无数据库:SQLiteDatabaseCorruptException:file is encrypted or is not a database

    如果有两个不同版本的DB就会出现此异常,如果DB破损(多次插拔SD卡,导致部分文件破损),也有可能出现此异常
    
  • WebView中SQLite缓存导致的崩溃:

    SQLiteDiskIOException:disk I/O error…at android.webkit.WebViewDatabase$1.run(WebViewDatabase.java:1000)

    WebView中存在着两种缓存:网页数据缓存(存储打开过的页面及资源)和Html5缓存(appcache)
    
    WebView自带的缓存机制里,会将url保存在webviewCashe.db中,将url内容保存在webviewCashe文件夹下,而对于database目录下的webview.db和webviewCashe.db都会自动生成1个名为android_metadata的表,只要创建SQLite数据库中的表,就会自动创建这个表
    
    建议缓存策略为:判断是否有网络,有的话,使用LOAD_DEFAULT,无网络时,使用LOAD_CACHE_ELSE_NETWORK
    
  • 磁盘读写错误:android.database.sqlite.SQLiteDiskIOException:disk I/O error(code 1802)

    dbHelper只有在创建数据库、进行事务处理时才会锁住数据库,默认情况下,dbHelper会缓存DB实例,执行类似于getWritableDatabase的操作是立即返回的,并不会上锁
    
    disk I/O error这类异常的抛出是因为多线程修改DB
    
  • android_metadata表不存在:

android.database.sqlite.SQLiteException:no such table:android_metadata SQLiteOpenHelper.getReadableDatabase

    开发中需要连接SQLite数据库,当使用如下方法打开数据库时就会抛出上述错误:

    SQLiteDatabase database = SQLiteDatabase.openDatabase(PATH,null,SQLiteDatabase.OPEN_READONLY);

    解决方法是将openDatabase方法中的最后一个参数改为SQLiteDatabase.NO_LOCALIZED_COLLATORS
  • android_metadata表中的locale字段:android.database.sqlite.SQLiteException:Failed to change locale for db ‘/data/data/appname/databases/webview.db’ to ‘zh_CN’

  • 数据库或磁盘满了:android.database.sqlite.SQLiteFullException:database or disk is full

  • 内存溢出:OutOfMemoryException

    在AndroidManifest.xml中有个参数可以设置:
    
        
  • Verify Failed:java.lang.VerifyError:Rejecting class xxx.package.activityA that attempts to sub-class erroneous class xxx.package.Activity 基类

其他情况的异常

  • TimeoutException:com.android.internal.BinderInternal$GcWatcher.finalize() timed out after 10 seconds

    GC回收超时会抛出此异常,注意重写finalize方法时不要有超时的操作
    
  • JSON解析异常:org.json.JSONException:No value for UserName at org.json.JSONObject.get(JSONObject.java:354) at…

    在解析JSON的时候,使用了getString("UserName"),如果不存在此key就会抛出上述异常,可以使用optString("UserName")、optJsonArray方法
    
  • JSONArray在初始化时为空

    java.lang.NullPointerException at
    org.json.JSONTokener.nextCleanInternal(JSONTokener.java:116) at
    org.json.JSONTokener.nextValue(JSONTokener.java:94)t…

  • 两个不同类型的View有相同的id

    java.lang.IllegalArgumentException:Wrong state class,expecting View State but received class android.widget.ScrollView$SavedState instead.This usually happens when two views of different type have the same id in the same hierarchy.This view’s id is id/0xff0000.Make sure other views do not use the same id.

    建议最好保证每个View的id都是唯一的,至少在一个布局文件中是唯一的
    
  • LayoutInfiater.from().infiate()使用不当导致的崩溃:No package identifier when getting value for resource number 0x00000001

    在程序中使用LayoutInfiater.from().infiate()语句时,必须写在具体的子类中,一定不能工作在父类或虚类里
    
    可以参考:http://blog.csdn.net/yanzi1225627/article/details/37338565
    
  • ViewGroup中的玄机:java.lang.IllegalArgumentException:parameter must be a descendant of this view

    这个崩溃是ViewGroup的offsetRectBetweenParentAndChild方法抛出来的
    
    可以参考:http://blog.sina.com.cn/s/blog_5704bfaf0102v3bn.html
    
  • 图片缩放很多倍:java.lang.IllegalArgumentException:bitmap size exceeds 32bits

    当图片缩放了很多倍时,导致内存溢出,就会抛出此异常,多发生在全屏显示一张图片的时候
    
  • 图片宽高为0:

    java.lang.IllegalArgumentException:width and height must be>0 at android.graphics.Bitmap.nativeCreate(Native Method)

    通常是因为没有取到图片的宽和高(缓存数据被清空或提前调用了获取图片宽高的方法),返回默认值0抛出此异常
    
  • 不能重复添加组件:View xxx has already been added to the window manager

    解决方法:
    try{
        windowmanager.removeView(view);
    }catch(IllegalStateException e){
        e.printStackTrace();
    }
    
    try{
        windowmanager.addView(view);
    }catch(IllegalStateException e){
        e.printStackTrace();
    }
    

你可能感兴趣的:(实用技能学习)