之前整理過一些關于常見的錯誤日志,基于生產(chǎn)的bug日志系統(tǒng),我這邊會不間斷的更新錯誤日志及相應的解決方案,拋磚引玉(PS:也許解決的方法有點菜,希望大家能給出更優(yōu)的解決方案及意見反饋,非常歡迎,相互學習共同進步)
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:635)
at android.view.ColorViewRootImpl.setView(ColorViewRootImpl.java:60)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:321)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:86)
at android.widget.PopupWindow.invokePopup(PopupWindow.java:1262)
at android.widget.PopupWindow.showAsDropDown(PopupWindow.java:1110)
at android.widget.PopupWindow.showAsDropDown(PopupWindow.java:1069)
以上bug出現(xiàn)的原因是因為PopupWindow需要依附在一個創(chuàng)建好的Activity上,那么出現(xiàn)這個異常就說明此時你的Activity還沒有創(chuàng)建好,出現(xiàn)這種情況,很可能是在onCreate()或者是onStart()中調(diào)用導致的。
下面有兩種方法可以解決這個問題:
方法一:重載Activity的onWindowFocusChanged方法,然后在里面實現(xiàn)相應的邏輯如下:
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus) {
//執(zhí)行PopupWindow相應的操作
}
}
下面給大家看下這個方法的源碼,有興趣的小伙伴可以看看
/**
* Called when the current {@link Window} of the activity gains or loses
* focus. This is the best indicator of whether this activity is visible
* to the user. The default implementation clears the key tracking
* state, so should always be called.
*
* <p>Note that this provides information about global focus state, which
* is managed independently of activity lifecycles. As such, while focus
* changes will generally have some relation to lifecycle changes (an
* activity that is stopped will not generally get window focus), you
* should not rely on any particular order between the callbacks here and
* those in the other lifecycle methods such as {@link #onResume}.
*
* <p>As a general rule, however, a resumed activity will have window
* focus... unless it has displayed other dialogs or popups that take
* input focus, in which case the activity itself will not have focus
* when the other windows have it. Likewise, the system may display
* system-level windows (such as the status bar notification panel or
* a system alert) which will temporarily take window input focus without
* pausing the foreground activity.
*
* @param hasFocus Whether the window of this activity has focus.
*
* @see #hasWindowFocus()
* @see #onResume
* @see View#onWindowFocusChanged(boolean)
*/
public void onWindowFocusChanged(boolean hasFocus) {
}
方法二:上面的那種方法是需要實現(xiàn)Activity的一個方法并在方法中做操作,一般我們在項目中會在一些邏輯里面showPopupWindow或者其他的,那這樣就會影響一些,然后我們就針對這個源碼,追溯一下會發(fā)現(xiàn)另外一個方法:hasWindowFocus
/**
* Returns true if this activity's <em>main</em> window currently has window focus.
* Note that this is not the same as the view itself having focus.
*
* @return True if this activity's main window currently has window focus.
*
* @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
*/
public boolean hasWindowFocus() {
Window w = getWindow();
if (w != null) {
View d = w.getDecorView();
if (d != null) {
return d.hasWindowFocus();
}
}
return false;
}
查看上面的源碼,我們會發(fā)現(xiàn),我們可以直接使用hasWindowFocus來判斷當前的Activity有沒有創(chuàng)建好,再去做其他操作;以上就是這個錯誤日志相應的解決方案,如果還有其他的希望大家補充。
java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
這個問題是在使用Glide的時候生產(chǎn)上面爆出來的,如果遇到其他相似的錯誤也可以試一下,以下有兩種解決方案:
方法一:參考博文
在使用Glide的地方加上這個判斷;Util是系統(tǒng)自帶的;
if(Util.isOnMainThread()) {
Glide.with(AppUtil.getContext()).load``(R.mipmap.iclunch).error(R.mipmap.cuowu).into(imageView);
}
在使用的Glide的界面的生命周期onDestroy中添加如下代碼:
@Override
protected void onDestroy() {
super.onDestroy();
if(Util.isOnMainThread()) {
Glide.with(this).pauseRequests();
}
}
上面Destroy中with(this),改成with(AppUtil.getContext());
不然會報: java.lang.IllegalStateException: Activity has been destroyed
擴展:
Glide.with(AppUtil.getContext()).resumeRequests()和 Glide.with(AppUtil.getContext()).pauseRequests()的區(qū)別:
1.當列表在滑動的時候,調(diào)用pauseRequests()取消請求;
2.滑動停止時,調(diào)用resumeRequests()恢復請求;
另外Glide.clear():當你想清除掉所有的圖片加載請求時,這個方法可以用到。
ListPreloader:如果你想讓列表預加載的話,可以試試這個類。
請記住一句話:不要再非主線程里面使用Glide加載圖片,如果真的使用了,請把context參數(shù)換成getApplicationContext;
方法二:使用Activity提供的isFinishing和isDestroyed方法,來判斷當前的Activity是不是已經(jīng)銷毀了或者說正在finishing,下面貼出來相應的源碼:
/**
* Check to see whether this activity is in the process of finishing,
* either because you called {@link #finish} on it or someone else
* has requested that it finished. This is often used in
* {@link #onPause} to determine whether the activity is simply pausing or
* completely finishing.
*
* @return If the activity is finishing, returns true; else returns false.
*
* @see #finish
*/
public boolean isFinishing() {
return mFinished;
}
/**
* Returns true if the final {@link #onDestroy()} call has been made
* on the Activity, so this instance is now dead.
*/
public boolean isDestroyed() {
return mDestroyed;
}
附上項目相應的源碼,希望有所幫助:
final WeakReference<ImageView> imgBankLogoWeakReference = new WeakReference<>(imgBankLogo);
final WeakReference<ImageView> imgBankBgWeakReference = new WeakReference<>(imgBankBg);
ImageView imgBankLogoTarget = imgBankLogoWeakReference.get();
ImageView imgBankBgTarget = imgBankBgWeakReference.get();
if (imgBankLogoTarget != null && imgBankBgTarget != null) {
if (!(isFinishing() || isDestroyed())) {
Glide.with(xxx.this).load(_bankCardInfo.getBankInfo().getBankLogo())
.centerCrop().into(imgBankLogoTarget);
Glide.with(xxx.this).load(_bankCardInfo.getBankInfo().getBankBg())
.centerCrop().into(imgBankBgTarget);
}
}
java.lang.IllegalStateException: Could not find a method OnButtonClick(View) in the activity class android.view.ContextThemeWrapper for onClick handler on view class android.widget.ImageView with id 'img_apply_result'
at android.view.View$1.onClick(View.java:4061)
at android.view.View.performClick(View.java:4848)
at android.view.View$PerformClick.run(View.java:20262)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5714)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:984)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
Caused by: java.lang.NoSuchMethodException: OnButtonClick [class android.view.View]
at java.lang.Class.getMethod(Class.java:664)
at java.lang.Class.getMethod(Class.java:643)
at android.view.View$1.onClick(View.java:4054)
解決方法:
以上的錯誤日志出現(xiàn)的有點low,但是呢有時候部分人還是容易忽略:我們一般在Activity或fragment等Java代碼中使用資源文件時,例如:在Java代碼中對一個Imageview附一張圖片,我們不能img.setImageResource(圖片相應的資源ID);需要img.setImageResource(context.getResources().getDrawable(圖片相應的資源ID));需要先獲取文件資源,再去拿圖片,但是剛剛寫的那個方法現(xiàn)在已經(jīng)過時了,下面我貼出Google官方給出的最新的方法img.setImageDrawable(ContextCompat.getDrawable(context, imgResId));其實setImageDrawable是最省內(nèi)存高效的,如果擔心圖片過大或者圖片過多影響內(nèi)存和加載效率,可以自己解析圖片然后通過調(diào)用setImageDrawable方法進行設置
java.lang.NoClassDefFoundError: android.app.AppOpsManager
Appops是Application Operations的簡稱,是關于應用權限管理的一套方案,但這里的應用指的是系統(tǒng)應用,這些API不對第三方應用開放。Appops的兩個重要組成部分是AppOpsManager和AppOpsService,它們是典型的客戶端和服務端設計,通過Binder跨進程調(diào)用。AppOpsManager提供標準的API供APP調(diào)用,但google有明確說明,大部分只針對系統(tǒng)應用。AppOpsService是做最終檢查的系統(tǒng)服務,它的注冊名字是appops, 應用可以類似于
mAppOps=(AppOpsManager)getContext().getSystemService(Context.APP_OPS_SERVICE);的方式來獲取這個服務。
解決方法:這個api是在19新加入的,所以要注意加個判斷,參考項目代碼如下:
判斷是否開啟通知權限(解釋比較好的博客推薦)
private boolean isNotificationEnabled(Context context) {
String CHECK_OP_NO_THROW = "checkOpNoThrow";
String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
if (Build.VERSION.SDK_INT < 19) {
return true;
}
try {
AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
ApplicationInfo appInfo = context.getApplicationInfo();
String pkg = context.getApplicationContext().getPackageName();
int uid = appInfo.uid;
Class appOpsClass = null;
/* Context.APP_OPS_MANAGER */
appOpsClass = Class.forName(AppOpsManager.class.getName());
Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
String.class);
Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
int value = (Integer) opPostNotificationValue.get(Integer.class);
return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoClassDefFoundError e) {
e.printStackTrace();
}
return false;
}
java.lang.SecurityException: getDeviceId: Neither user 10185 nor current process has android.permission.READ_PHONE_STATE.
這里的getDeviceId可能是獲取系統(tǒng)狀態(tài)或內(nèi)容的操作,需要授予android.permission.READ_PHONE_STATE 權限,首先我們來看一下危險權限組
我們會發(fā)現(xiàn)android.permission.READ_PHONE_STATE 這個權限在PHONE組里面,在Android M版本及以后,當你的應用運行在Android6.0系統(tǒng)上如果設置targetSdkVersion小于23的時候,它也會默認采用以前的權限管理機制,當你的targetSdkVersion大于等于23的時候且在Andorid6.0(M)系統(tǒng)上,它會采用新的這套權限管理機制。相關動態(tài)權限爬坑這塊可以看一下之前的博文(傳送門)
,當你配置了targetSdkVersion>=23時,默認第一次安裝會打開android.permission.READ_PHONE_STATE這個權限,部分手機親測,那樣依舊可以獲取getDeviceId,但這個權限是可見的,用戶在后續(xù)是可以關閉的。當用戶關閉了這個權限,下次進來會動態(tài)彈出授權頁面提醒用戶授權,如果用戶依舊關閉權限將獲取不到DeviceId。但是國產(chǎn)手機的各種自定義導致部分手機會出現(xiàn)動態(tài)權限返回0,(PS:當用戶禁止了權限,返回回調(diào)還是為已授權,例如:OPPO meizu等兼容),這樣就尷尬了,如果我們拿到用戶已經(jīng)授權(但實際上是禁止的)就去調(diào)用
TelephonyManager tm = (TelephonyManager) getApplicationContext()
.getSystemService(Context.TELEPHONY_SERVICE);
_clientInfo.setDeviceId(tm.getDeviceId());
就會閃退,目前這邊處理的思路為:第一次如果拿到就放在SharedPreferences里面存起來,當下次用戶再次關閉權限也不用擔心報錯;
java.util.ConcurrentModificationExceptionat java.util.ArrayList$ArrayListIterator.next(ArrayList.java:578)
at com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter.serialize(DefaultTypeAdapters.java:637)
at com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter.serialize(DefaultTypeAdapters.java:624)
at com.google.gson.JsonSerializationVisitor.findAndInvokeCustomSerializer(JsonSerializationVisitor.java:184)
at com.google.gson.JsonSerializationVisitor.visitUsingCustomHandler(JsonSerializationVisitor.java:160)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:101)
at com.google.gson.JsonSerializationContextDefault.serialize(JsonSerializationContextDefault.java:62)
at com.google.gson.JsonSerializationContextDefault.serialize(JsonSerializationContextDefault.java:53)
at com.google.gson.Gson.toJsonTree(Gson.java:220)
at com.google.gson.Gson.toJson(Gson.java:260)
at com.google.gson.Gson.toJson(Gson.java:240)
在ArrayList.addAll()中對傳進來的參數(shù)沒有做null判斷,于是,在調(diào)用collection.toArray()函數(shù)的時候就拋異常了,activity就崩潰了
在使用ArrayList.addAll()的時候一定要注意傳入的參數(shù)會不會出現(xiàn)為null的情況,如果有,那么我們可以做以下判斷
if (collection!= null)
mInfoList.addAll(Collection<? extends E> collection);
如果為null,就不執(zhí)行下面的了,我們也不能確保是不是存在null的情況,所以為了確保不會出錯,在前面加個判斷是一個有經(jīng)驗的程序員該做的。以上錯誤日志的原因,可以看下源碼大家就可以理解了:(這個問題雖小但容易忽略,希望各位注意)
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the
* specified collection's Iterator. The behavior of this operation is
* undefined if the specified collection is modified while the operation
* is in progress. (This implies that the behavior of this call is
* undefined if the specified collection is this list, and this
* list is nonempty.)
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
android.content.ActivityNotFoundException: Unable to find explicit activity class {com.android.browser/com.android.browser.BrowserActivity}; have you declared this activity in your AndroidManifest.xml?
以上錯誤日志出現(xiàn)的背景是調(diào)用系統(tǒng)自帶的瀏覽器出現(xiàn)的,原因是因為部分手機設備商更改Android原生自帶的com.android.browser/com.android.browser.BrowserActivity自己弄了一個其他的,例如,生產(chǎn)就出現(xiàn)一款手機 HTC 802t,這款手機自帶瀏覽器的代碼包名為:com.htc.sense.browser,看到這塊是不是想吐槽一下,所以說如果直接寫以下代碼,就會出現(xiàn)以上錯誤日志:
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.addCategory(Intent.CATEGORY_BROWSABLE);
Uri contentUri = Uri.parse(_versionInfo.getAppUrl());
intent.setData(contentUri);
intent.setComponent(new ComponentName("com.android.browser", "com.android.browser.BrowserActivity"));
解決方案(PS:獲取系統(tǒng)安裝的所有的瀏覽器應用 過濾):
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.addCategory(Intent.CATEGORY_BROWSABLE);
Uri contentUri = Uri.parse(_versionInfo.getAppUrl());
intent.setData(contentUri);
// HTC com.htc.sense.browser
List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);//通過查詢,獲得所有ResolveInfo對象.
for (ResolveInfo resolveInfo : resolveInfos) {
browsers.add(resolveInfo.activityInfo.packageName);
System.out.println(resolveInfo.activityInfo.packageName);
}
if (browsers.contains("com.android.browser")) {
intent.setComponent(new ComponentName("com.android.browser", "com.android.browser.BrowserActivity"));
}
context.startActivity(intent);
android.os.FileUriExposedException/NullPointerException: Attempt to invoke virtual method 'java.lang.String android.net.Uri.getPath()' on a null object reference:
上述錯誤日志是Android 7.0應用間共享文件(FileProvider)兼容的問題,后續(xù)會出一篇博文來講解:
下面提供代碼:
/**
* 適配7.0及以上
*
* @param context
* @param file
* @return
*/
private static Uri getUriForFile(Context context, File file) {
if (context == null || file == null) {
throw new NullPointerException();
}
Uri uri;
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(context.getApplicationContext(), "xxx.fileprovider", file);
} else {
uri = Uri.fromFile(file);
}
return uri;
}
AndroidManifest中配置provider:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.crfchina.market.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
下面是很久之前的備忘的,也貼出來給大家分享一下??赡苌婕暗狡渌┪牡膬?nèi)容,如有發(fā)現(xiàn),麻煩私信,我后續(xù)加上 ……
android java.net.UnknownHostException: Unable to resolve host "...": No address associated 錯誤
解決方法:
(1)手機3G或者WIFI沒有開啟
-
(2).Manifest文件沒有標明網(wǎng)絡訪問權限
如果確認網(wǎng)絡已經(jīng)正常連接并且還是出這種錯誤的話,那么請看下你的Manifest文件是否標明應用需要網(wǎng)絡訪問權限,如果沒標明的話,也訪問不了網(wǎng)絡,也會造成這種情況的.//網(wǎng)絡訪問權限
<uses-permission android:name="android.permission.INTERNET" />
java.lang.NullPointerException: missing IConnectivityManager
at com.android.internal.util.Preconditions.checkNotNull(Preconditions.java:52)
at android.net.ConnectivityManager.<init>(ConnectivityManager.java:919)
at android.app.ContextImpl$11.createService(ContextImpl.java:387)
at android.app.ContextImpl$ServiceFetcher.getService(ContextImpl.java:278)
at android.app.ContextImpl.getSystemService(ContextImpl.java:1676)
at android.content.ContextWrapper.getSystemService(ContextWrapper.java:540)
at com.crfchina.market.util.NetUtil.getNetworkState(NetUtil.java:28)
錯誤日志產(chǎn)生原因:
Android里面內(nèi)存泄漏問題最突出的就是Activity的泄漏,而泄漏的根源大多在于單例的使用,也就是一個靜態(tài)實例持有了Activity的引用。靜態(tài)變量的生命周期與應用(Application)是相同的,而Activity生命周期通常比它短,也就會造成在Activity生命周期結束后,還被引用導致無法被系統(tǒng)回收釋放。
生成靜態(tài)引用內(nèi)存泄漏可能有兩種情況:
1. 應用級:應用程序代碼實現(xiàn)的單例沒有很好的管理其生命周期,導致Activity退出后仍然被引用。
2. 系統(tǒng)級:Android系統(tǒng)級的實現(xiàn)的單例,被應用不小心錯誤調(diào)用(當然你也可以認為是系統(tǒng)層實現(xiàn)地不太友好)。
這個主要講下系統(tǒng)級的情況,這樣的情況可能也有很多,舉個最近發(fā)現(xiàn)的問題ConnectivityManager。
通常我們獲取系統(tǒng)服務時采用如下方式:
context.getSystemService()
在Android6.0系統(tǒng)上,如果這里的Context如果是Activity的實例,那么即使你什么也不干也會造成內(nèi)存泄漏。
這個Context在ConnectivityManager 創(chuàng)建時傳入,這個Context在StaticOuterContextServiceFetcher中由ContextImpl對象轉換為OuterContext,與就是Activity對象,所以最終ConnectivityManager的單實例持有了Activity的實例引用。這樣即使Activity退出后仍然無法釋放,導致內(nèi)存泄漏。
這個問題僅在6.0上出現(xiàn),在5.1上ConnectivityManager實現(xiàn)為單例但不持有Context的引用,在5.0有以下版本ConnectivityManager既不為單例,也不持有Context的引用。
其他服務沒認真研究,不確定有沒有這個問題。不過為了避免類似的情況發(fā)生,
最好的解決辦法就是:
解決方案:
獲取系統(tǒng)服務getSystemService時使用ApplicationContext
context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
java.lang.IllegalArgumentException: View not attached to window manager
錯誤日志產(chǎn)生原因:
在延時線程里調(diào)用了ProgressDialog.dismiss,但此時主Activity已經(jīng)destroy了。于是應用崩潰,我寫了一個 SafeProgressDialog 來避免這個問題,主要原理是覆寫dismiss方法,在ProgressDialog.dismiss之前判斷Activity是否存在。
解決方案:
class SafeProgressDialog extends ProgressDialog
{
Activity mParentActivity;
public SafeProgressDialog(Context context)
{
super(context);
mParentActivity = (Activity) context;
}
@Override
public void dismiss()
{
if (mParentActivity != null && !mParentActivity.isFinishing())
{
super.dismiss(); //調(diào)用超類對應方法
}
}
}
Android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.test.testFragment: make sure class name exists, is public, and has an empty constructor that is public
錯誤日志產(chǎn)生原因及解決方案:
根據(jù)報錯提示 “make sure class name exists, is public, and has an empty constructor that is public” ,若Fragement定義有帶參構造函數(shù),則一定要定義public的默認的構造函數(shù)。即可解決此問題。
除了他說的public.還有一個就是弄個空的構造函數(shù)。
例如我是這樣定義了我的fragment。帶有了構造函數(shù)
public TestFragment(int index){
mIndex = index;
}
然后我添加了一個這樣的構造函數(shù)
public TestFragment(){
}
java.lang.IllegalStateException: Unable to get package info for com.crfchina.market; is package not installed?
錯誤日志產(chǎn)生原因:
簡單的卸載app 沒有卸載干凈,然后再次運行,當dalvik重新安裝。apk文件并試圖重用以前的活動從同一個包
好了目前就總結這么多,后續(xù)還會繼續(xù)更新補充!畢竟太長也沒有人愿意耐下心去看,以上也是曾經(jīng)遇到過坑,希望有遇到的兄弟能從中受益!歡迎大家貼一些內(nèi)容作為補充,相互學習共同進步……