最近优化项目,就改了bugly上报的一些异常,写个文章记录一下,以后忘记了可以查阅。
一、java.lang.NullPointerException
该异常表示尝试去调用virtual method,使用了一个空对象引用,建议检查引用的对象是否为空。
[解决方案]:这种异常通常是调用一个对象的方法抛出的,凡是调用一个对象的方法之前,一定要进行判空或者进行try-catch,这样基本可以规避大部分空指针异常。
二、java.lang.IndexOutOfBoundsException 或java.lang.ArrayIndexOutOfBoundsException
该异常表示不合法下标,通常是因为访问了list或array不合法的位置,建议检查数据是否越界。
[解决方案]:
1.遍历数组/字符串等集合前,要判断遍历对象的长度;
2.操作数组/字符串等集合前,要检查角标是否在长度允许范围内;
2.ListView操作不当也会引起该异常,这种情况下一般是由于List渲染的时候,外面的数据源发生变化导致的。举例如ListView滚动时点击刷新将会报错,解决方法是ListView滚动时将刷新置为不可点击。
三、java.lang.NumberFormatException
[解决方案]:
数字格式化异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常(字符串不能为""或null)
四、java.lang.ClassNotFoundException
[解决方案]:
该异常表示在路径下,找不到指定类,通常是因为构建路径问题导致的。
[解决方案]:类名是以字符串形式标识的,可信度比较低,在调用Class.forName(""),Class.findSystemClass(""),Class.loadClass("")等方法时,找不到类名时将会报错。如果找不到的Class是系统Class,那么可能是系统版本兼容,厂家Rom兼容的问题,找到对应的设备尝试重现,解决方法可以考虑更换Api,或用自己实现的Class替代。
如果找不到的Class是应用自由Class(含第三方SDK的Class),可以通过反编译工具查看对应apk中是否真的缺少该Class,再进行定位,这种往往发生在:
1.要找的Class被混淆了,存在但名字变了;
2.要找的Class未被打入Dex,确实不存在,可能是因为自己的疏忽,或编译环境的冲突;
3.要找的Class确实存在,但你的Classlorder找不到这个Class,往往因为这个Classloder是你自实现的(插件化应用中常见)。
五、java.util.ConcurrentModificationException
并发修改异常,该异常表示迭代器迭代过程中,迭代的对象发生了改变,如数据项增加或删除。
[解决方案]:由于迭代对象不是线程安全,在迭代的过程中,会检查modCount是否和初始modCount即expectedModCount一致,如果不一致,则认为数据有变化,迭代终止并抛出异常。常出现的场景是,两个线程同时对集合进行操作,线程1对集合进行遍历,而线程2对集合进行增加、删除操作,此时将会发生ConcurrentModificationException异常。
具体方法:多线程访问时要增加同步锁,或者建议使用线程安全的集合:
1. 使用ConcurrentHashMap替换HashMap,CopyOnWriteArrayList替换ArrayList;
2. 或者使用使用Vector替换ArrayList,Vector是线程安全的。Vector的缺点:大量数据操作时,由于线程安全,性能比ArrayList低.
六、java.lang.OutOfMemoryError
Could not allocate JNI Env
OutOfMemoryError异常的常见原因有以下几种:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
解决方案
加载数据过多的时候选择分页加载,不要在循环中大量创建对象。
OOM是常见的java错误,OOM主要有:
1.OOM fo heapjava.lang:OutOfMemoryError: Java heap space,此OOM是由于JVM中heap的最大值不满足需要,将设置heap的最大值调高即可。
2.OOM for Perm:java.lang:OutOfMemoryError: Java perm space,此OOM是由于JVM中perm的最大值不满足需要,将设置perm的最大值调高即可,参数样例为:-XX:MaxPermSize=512M
3.OOM for GC=>例如:java.lang:OutOfMemoryError: GC overhead limit exceeded,此OOM是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略
4.OOM for native thread created:java.lang.OutOfMemoryError: unable to create new native thread,此OOM是由于进程剩余的空间不足,导致创建进程失败
5.OOM for allocate huge array:Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit,此类信息表明应用程序(或者被应用程序调用的APIs)试图分配一个大于堆大小的数组
6.OOM for small swap:Exception in thread "main": java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?,抛出这类错误,是由于从native堆中分配内存失败,并且堆内存可能接近耗尽
7.OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available,抛出这类错误,一般是由于方法重复调用、死循环引起,直至内存耗尽
七、java.lang.IllegalArgumentException
View=DecorView@9de4b14[MainActivity] not attached to window manager
android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:547)
该异常表示view没有添加到窗口管理器,通常是我们dismiss对话框的时候,activity已经不存在了,建议不要在非UI线程操作对话框。
[解决方案]:常发生这类Exception的情形都是,有一个费时的线程操作,需要显示一个Dialog,在任务开始的时候显示一个对话框,然后当任务完成了在Dismiss对话框,如果在此期间如果Activity因为某种原因被杀掉且又重新启动了,那么当dialog调用dismiss的时候WindowManager检查发现Dialog所属的Activity已经不存在,所以会报错。
要避免此类Exception,就要正确的使用对话框,也要正确的使用线程:
1.不要在非UI线程中使用对话框创建,显示和取消对话框;
2.尽量少用单独线程,出发是真正的耗时操作采用线程,线程也不要直接用Java式的匿名线程,除非是那种单纯的操作,操作完成不需要做其他事情的。
3.如果是在fragment中发起异步网络的回调中进行dialog的操作,那么在操作之前,需要判断 isAdd( ),避免fragment被回收了但是还要求dialog去dismiss
4.在Activity onDestroy中对Dialog提前进行关闭
八、java.lang.NoSuchFieldError
No static field general_type_item_video_call of type I in class Lcom/feinnoui/library/R$string; or its superclasses (declaration of 'com.feinnoui.library.R$string' appears in /data/app/com.bankcomm.jrt-2/split_lib_slice_8_apk.apk)
域不存在错误。当应用试图访问或者修改某类的某个域,而该类的定义中没有该域的定义时抛出该错误
NoSuchFieldError是Java反射中的一个异常,其表示无法通过反射找到需要的字段。
本次出现这个问题是项目中做了国际化适配,英文版的少了这个资源id,编译的时候没有出错,用户切为英文环境的时候,就报出了这个错。
[解决方案]:加上指定的资源。
九、java.lang.ClassCastException
类型转换异常。当试图将一个类型转换为指定的类型,而该类型却不是同一个类型时,抛出该异常
问题出现原因:JVM在做类型转换时会按照如下规则进行检查:
对于普通对象,对象必须是目标类的实例或目标类的子类的实例。如果目标类是接口,那么会把他当作实现了该接口的一个子类。
对于数组类型,目标类必须是数组类型或java.lang.Object、java.lang.Cloneable、java.io.Serializable。
如果不满足上面的规则,JVM会报这个错误了。
[解决方案]:先用 instanceof判断数据类型,然后再强转。
十、java.lang.IllegalStateException
java.lang.IllegalStateException异常产生的原因及解决办法
错误类型大致为以下几种:
1.java.lang.IllegalStateException:Cannot forward a response that is already committed
IllegalStateException:response already commited
IllegalStateException:getOutputStream() has already been called for this request
…………
IllegalStateException: Can not perform this action after onSaveInstanceState:
#解决办法:onSaveInstanceState方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后
再给它添加Fragment就会出错。解决办法就是把commit()方法替换成 commitAllowingStateLoss()
错误原因:
该异常表示,当前对客户端的响应已经结束,不能在响应已经结束(或说消亡)后再向客户端(实际上是缓冲区)输出任何内容。
Object is no longer valid to operate on. Was it deleted by another thread?
该异常表示,realmObject对象在其他线程已被删除,在这个线程中使用的时候抛出的异常。
具体分析:
首先解释下flush(),我们知道在使用读写流的时候数据先被读入内存这个缓冲区中, 然后再写入文件,但是当数据读完时不代表数据已经写入文件完毕,因为可能还有一部分仍未写入文件而留在内存中,这时调用flush()方法就会把缓冲区的数据强行清空输出,因此flush()的作用就是保证缓存清空输出。response是服务端对客户端请求的一个响应,其中封装了响应头、状态码、内容等,服务端在把response提交到客户端之前,会向缓冲区内写入响应头和状态码,然后将所有内容flush。这就标志着该次响应已经committed(提交)。对于当前页面中已经committed(提交)的response,就不能再使用这个response向缓冲区写任何东西(注:同一个页面中的response.XXX()是同一个response的不同方法,只要其中一个已经导致了committed,那么其它类似方式的调用都会导致 IllegalStateException异常)。
2.java.lang.IllegalStateException
Can't change tag of fragment d{e183845 #0 d{e183845}}: was d{e183845} now d{e183845 #0 d{e183845}}
经查,我在显示fragment的代码中使用了:
fragment.show(getSupportFragmentManager, fragment.toString());
而这里是因为两次toString()结果不同,导致不同的tag指向的是同一个fragment。
获取fragment的tag的正确方法应该是使用其提供的fragment.getTag()方法。
3.java.lang.IllegalStateException
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 37 path $.data
错误原因:该异常是由于服务器错误返回的JSON字符串和服务器正常下时返回的JSON字符串结构不同,导致利用Gson解析的时候报了一个异常:本该去解析集合却强制去解析对象所致.
解决办法:在使用Gson解析JSON时try cash一下,不报错按照正常逻辑继续解析,报异常则处理为请求失败逻辑即可.
数据库相关的Exception
4.android.database.CursorWindowAllocationException
Cursor window allocation of 2048 kb failed.
该异常表示游标窗口分配内存失败,通常是使用Cursor不当导致的内存泄漏。
[解决方案]:建议检查工程中所有使用Cursor对象的地方,保证使用后及时关闭。另外,加强内存泄露测试,可以提前发现此类问题。
5.java.lang.IllegalStateException
Not allowed to start service Intent { cmp=com.bankcomm.jrt/com.chinamobile.uc.emplistsdk.service.CoreService }: app is in background uid UidRecord{df3345e u0a128 LAST bg:+1m5s762ms idle procs:2 seq(0,0,0)}
错误原因:Android 8.0 不再允许后台service直接通过startService方式去启动。
[解决方案]:改为startForegroundService方式启动即可(注意:用此方法开启service,必须在5S内调用startForeground())。
十一、android.database.sqlite.SQLiteDatabaseLockedException
database is locked (Sqlite code 5): , while compiling: PRAGMA journal_mode, (OS error - 2:No such file or directory)
数据库文件被锁定,两个地方同时访问数据库的时候出现此异常。
[解决方案]:建议将 SQLiteOpenHelper 写成单例模式的,这样就能保证同一时间内只能有一个 SQLiteOpenHelper 访问 sqlite 数据库,避免同时有两个 SQLiteOpenHelper 访问 sqlite 报错的情况发生。
十二、net.sqlcipher.database.SQLiteException
file is not a database: , while compiling: select count(*) from sqlite_master;
文件不是db文件,在用sqlcipher进行加密后,重新调用未加密的库的时候出现的Exception
[解决方案]:
1.要求用户卸载安装,或者加密之后做好兼容,删除原有数据库文件,重新建库建表(优点,操作简单,缺点:丢失原有数据)。
2.读取旧数据库中的数据,复制表到新的数据库。
十三、java.lang.InternalError
Thread starting during runtime shutdown
java.lang.Thread.nativeCreate(Native Method)
android.app.RemoteServiceException
该异常表示发生了内部错误,而这个错误是线程在runtime宕机时启动造成的。
[解决方案]:这类问题一般是是线程开启的太晚了导致的。我们启动一个线程会调用Thread的start方法,start方法会调用名为nativeCreate的本地方法,具体位置在/android/art/runtime/native/java_lang_Thread.cc,名字换成了CreateNativeThread:我们可以看看它的源码:
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
CHECK(java_peer != nullptr);//即为java层的thread实例,包裹着run方法的具体实现
Thread* self = static_cast<JNIEnvExt*>(env)->self;
Runtime* runtime = Runtime::Current();
// Atomically start the birth of the thread ensuring the runtime isn't shutting down.
bool thread_start_during_shutdown = false;//这段代码用来检测thread是否在runtime宕机时start的
{
MutexLock mu(self, *Locks::runtime_shutdown_lock_);
if (runtime->IsShuttingDownLocked()) {
thread_start_during_shutdown = true;
} else {
runtime->StartThreadBirth();
}
}
if (thread_start_during_shutdown) {//若runtime宕机了就抛出异常
ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError"));
env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown");
return;
}
..... 省略代码
}
从代码我们可以知道,如果runtime宕机了就会抛出InternalError的异常。我们在启动线程的时候,需要确认一下是否有线程嵌套的情况,不要在线程中再去启动一个线程。
十四、Context.startForegroundService() did not then call Service.startForeground()
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1855)
问题出现场景:
远程服务异常,在android8.0开启服务时遇到,因为8.0对于服务的管理,不允许后台开启服务,所以必须把服务置于前台,这时候就必须在被开启服务的onCreate方法中调用startForeground()方法,如果在5S之内没有调这个方法,便会爆出上述异常。
该异常表示从Package发布通知时存在异常,主要出现在通知栏更新。
[解决方案]:出现这个异常的原因有很多,使用通知需要注意以下几点:
1.每次更新时都需要实例化Notification;
2.设置Notification的ContentView之后记得设置ContentIntent;
3.如果使用了RemoteViews时,需要保证包名的正确性;
4.注意各个系统版本对于Notification使用方法的差异性。
十五、android.os.RemoteException
Remote stack trace: at com.android.server.am.ActivityManagerService.killBackgroundProcesses(ActivityManagerService.java:7519) at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:1231) at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3632) at com.android.server.am.HwActivityManagerService.onTransact(HwActivityManagerService.java:628) at android.os.Binder.execTransact(Binder.java:739)
java.lang.RuntimeException:Unable to pause activity {com.bankcomm.jrt/com.feinnoui.library.ui.activitys.chat.SingleChatActivity}: java.lang.SecurityException: Permission Denial: killBackgroundProcesses() from pid=25580, uid=1010178 requires android.permission.KILL_BACKGROUND_PROCESSES
[解决方案]:缺少后台杀进程的权限KILL_BACKGROUND_PROCESSES,加上就好
十六、android.content.ActivityNotFoundException
Unable to find explicit activity class {com.bankcomm.jrt/com.feinnoui.library.ui.activitys.contact.LocalContactCardActivity}; have you declared this activity in your AndroidManifest.xml?
该异常表示找不到指定的Activity。
[解决方案]:
1.检查AndroidManifest文件,是否注册了对应的Activity:
<activity
android:name="com.tencent.bugly.testActivity"
android:label="@string/app_name">
</activity>"
2.系统版本或厂商兼容性问题,如在高版本中调用了低版本已经废弃的Activity,建议对这类问题加强适配测试,添加特殊处理;
3.通过反编译工具检查该Activity类是否已经打入Apk。
4.调起第三方应用界面也有可能出现此问题
十七、android.view.WindowManager$BadTokenException
Unable to add window -- token android.os.BinderProxy@9f75caa for displayid = 0 is not valid; is your activity running?
该异常出现原因是由于将要弹出的dialog所要依附的View已经不存在导致的。
出现场景:当界面销毁后再弹出来;或者界面跳转时我们的view发生改变,dialog依附的context发生变化或者界面未运行了。
此外,很多时候我们需要通过一个非组件类来调用一个view类的方法来弹出dialog或Toast,这样就需要再提供一个静态context来创建这个dialog或者Toast
[解决方案]:
1.对于tab页出现的错误可以用其父类的context来弹出dialog; 对于界面已经销毁引起的错误就只能判断界面是否存在然后再弹出了;
2.对于利用静态context来弹出的dialog可以通过规避的方式来解决,比如避免出现静态context被修改。。但是这样就可能限制了我们程序的功能。。
3.因此我们可以通过在bind数据时时时更新这个静态context就可以解决此问题了,这样就可以保证这个静态的context在任何view中都是当前的界面的view的context。就不会出现找不到其父类window了。
4.在关闭PopupWindow或者dialog时,要先关闭PopupWindow或者dialog,再关闭Activity,PopupWindow或者dialog不能独立存在。
十八、Only fullscreen activities can request orientation
问题出现原因,谷歌在安卓8.0版本时为了支持全面屏,增加了一个限制:如果是透明的Activity,则不能固定它的方向,因为它的方向其实是依赖其父Activity的(因为透明)。然而这个bug只有在8.0中有,8.1中已经修复。具体crash有两种:
1.Activity的风格为透明,在manifest文件中指定了一个方向,则在onCreate中crash
2.Activity的风格为透明,如果调用setRequestedOrientation方法固定方向,则crash
[解决方案]:参考:https://blog.youkuaiyun.com/starry_eve/article/details/82777160
持续更新~~~~