美文网首页Androidandroid兼容性问题Android开发
Android 常见的错误日志及相应的解决方案总结

Android 常见的错误日志及相应的解决方案总结

作者: 大荣言午 | 来源:发表于2017-07-26 09:02 被阅读450次

    之前整理过一些关于常见的错误日志,基于生产的bug日志系统,我这边会不间断的更新错误日志及相应的解决方案,抛砖引玉(PS:也许解决的方法有点菜,希望大家能给出更优的解决方案及意见反馈,非常欢迎,相互学习共同进步)

    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出现的原因是因为PopupWindow需要依附在一个创建好的Activity上,那么出现这个异常就说明此时你的Activity还没有创建好,出现这种情况,很可能是在onCreate()或者是onStart()中调用导致的。
    下面有两种方法可以解决这个问题:

    方法一:重载Activity的onWindowFocusChanged方法,然后在里面实现相应的逻辑如下:

    public void onWindowFocusChanged(boolean hasFocus) {  
        super.onWindowFocusChanged(hasFocus);  
        if(hasFocus) {  
            //执行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) {
    }
    

    方法二:上面的那种方法是需要实现Activity的一个方法并在方法中做操作,一般我们在项目中会在一些逻辑里面showPopupWindow或者其他的,那这样就会影响一些,然后我们就针对这个源码,追溯一下会发现另外一个方法: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;
    }
    

    查看上面的源码,我们会发现,我们可以直接使用hasWindowFocus来判断当前的Activity有没有创建好,再去做其他操作;以上就是这个错误日志相应的解决方案,如果还有其他的希望大家补充。

    java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity

    这个问题是在使用Glide的时候生产上面爆出来的,如果遇到其他相似的错误也可以试一下,以下有两种解决方案:

    方法一:参考博文
    在使用Glide的地方加上这个判断;Util是系统自带的;

    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()的区别:
    1.当列表在滑动的时候,调用pauseRequests()取消请求;
    2.滑动停止时,调用resumeRequests()恢复请求;
    另外Glide.clear():当你想清除掉所有的图片加载请求时,这个方法可以用到。
    ListPreloader:如果你想让列表预加载的话,可以试试这个类。
    请记住一句话:不要再非主线程里面使用Glide加载图片,如果真的使用了,请把context参数换成getApplicationContext;

    方法二:使用Activity提供的isFinishing和isDestroyed方法,来判断当前的Activity是不是已经销毁了或者说正在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)

    解决方法:

    以上的错误日志出现的有点low,但是呢有时候部分人还是容易忽略:我们一般在Activity或fragment等Java代码中使用资源文件时,例如:在Java代码中对一个Imageview附一张图片,我们不能img.setImageResource(图片相应的资源ID);需要img.setImageResource(context.getResources().getDrawable(图片相应的资源ID));需要先获取文件资源,再去拿图片,但是刚刚写的那个方法现在已经过时了,下面我贴出Google官方给出的最新的方法img.setImageDrawable(ContextCompat.getDrawable(context, imgResId));其实setImageDrawable是最省内存高效的,如果担心图片过大或者图片过多影响内存和加载效率,可以自己解析图片然后通过调用setImageDrawable方法进行设置

    java.lang.NoClassDefFoundError: android.app.AppOpsManager

    Appops是Application Operations的简称,是关于应用权限管理的一套方案,但这里的应用指的是系统应用,这些API不对第三方应用开放。Appops的两个重要组成部分是AppOpsManager和AppOpsService,它们是典型的客户端和服务端设计,通过Binder跨进程调用。AppOpsManager提供标准的API供APP调用,但google有明确说明,大部分只针对系统应用。AppOpsService是做最终检查的系统服务,它的注册名字是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可能是获取系统状态或内容的操作,需要授予android.permission.READ_PHONE_STATE 权限,首先我们来看一下危险权限组

    我们会发现android.permission.READ_PHONE_STATE 这个权限在PHONE组里面,在Android M版本及以后,当你的应用运行在Android6.0系统上如果设置targetSdkVersion小于23的时候,它也会默认采用以前的权限管理机制,当你的targetSdkVersion大于等于23的时候且在Andorid6.0(M)系统上,它会采用新的这套权限管理机制。相关动态权限爬坑这块可以看一下之前的博文(传送门)
    ,当你配置了targetSdkVersion>=23时,默认第一次安装会打开android.permission.READ_PHONE_STATE这个权限,部分手机亲测,那样依旧可以获取getDeviceId,但这个权限是可见的,用户在后续是可以关闭的。当用户关闭了这个权限,下次进来会动态弹出授权页面提醒用户授权,如果用户依旧关闭权限将获取不到DeviceId。但是国产手机的各种自定义导致部分手机会出现动态权限返回0,(PS:当用户禁止了权限,返回回调还是为已授权,例如:OPPO meizu等兼容),这样就尴尬了,如果我们拿到用户已经授权(但实际上是禁止的)就去调用

    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()中对传进来的参数没有做null判断,于是,在调用collection.toArray()函数的时候就抛异常了,activity就崩溃了

    在使用ArrayList.addAll()的时候一定要注意传入的参数会不会出现为null的情况,如果有,那么我们可以做以下判断

    if (collection!= null)
    mInfoList.addAll(Collection<? extends E> collection);
    

    如果为null,就不执行下面的了,我们也不能确保是不是存在null的情况,所以为了确保不会出错,在前面加个判断是一个有经验的程序员该做的。以上错误日志的原因,可以看下源码大家就可以理解了:(这个问题虽小但容易忽略,希望各位注意)

    /**
     * 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?

    以上错误日志出现的背景是调用系统自带的浏览器出现的,原因是因为部分手机设备商更改Android原生自带的com.android.browser/com.android.browser.BrowserActivity自己弄了一个其他的,例如,生产就出现一款手机 HTC 802t,这款手机自带浏览器的代码包名为:com.htc.sense.browser,看到这块是不是想吐槽一下,所以说如果直接写以下代码,就会出现以上错误日志:

     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:获取系统安装的所有的浏览器应用 过滤):

    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)兼容的问题,后续会出一篇博文来讲解:
    下面提供代码:

    /**
     * 适配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>
    

    下面是很久之前的备忘的,也贴出来给大家分享一下。可能涉及到其他博文的内容,如有发现,麻烦私信,我后续加上 ……

    android java.net.UnknownHostException: Unable to resolve host "...": No address associated 错误

    解决方法:

    • (1)手机3G或者WIFI没有开启

    • (2).Manifest文件没有标明网络访问权限
      如果确认网络已经正常连接并且还是出这种错误的话,那么请看下你的Manifest文件是否标明应用需要网络访问权限,如果没标明的话,也访问不了网络,也会造成这种情况的.

      //网络访问权限

      <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)
    

    错误日志产生原因:

    Android里面内存泄漏问题最突出的就是Activity的泄漏,而泄漏的根源大多在于单例的使用,也就是一个静态实例持有了Activity的引用。静态变量的生命周期与应用(Application)是相同的,而Activity生命周期通常比它短,也就会造成在Activity生命周期结束后,还被引用导致无法被系统回收释放。
    生成静态引用内存泄漏可能有两种情况:

    1. 应用级:应用程序代码实现的单例没有很好的管理其生命周期,导致Activity退出后仍然被引用。
    2. 系统级:Android系统级的实现的单例,被应用不小心错误调用(当然你也可以认为是系统层实现地不太友好)。
    

    这个主要讲下系统级的情况,这样的情况可能也有很多,举个最近发现的问题ConnectivityManager。
    通常我们获取系统服务时采用如下方式:
    context.getSystemService()
    在Android6.0系统上,如果这里的Context如果是Activity的实例,那么即使你什么也不干也会造成内存泄漏。
    这个Context在ConnectivityManager 创建时传入,这个Context在StaticOuterContextServiceFetcher中由ContextImpl对象转换为OuterContext,与就是Activity对象,所以最终ConnectivityManager的单实例持有了Activity的实例引用。这样即使Activity退出后仍然无法释放,导致内存泄漏。
    这个问题仅在6.0上出现,在5.1上ConnectivityManager实现为单例但不持有Context的引用,在5.0有以下版本ConnectivityManager既不为单例,也不持有Context的引用。
    其他服务没认真研究,不确定有没有这个问题。不过为了避免类似的情况发生,
    最好的解决办法就是:

    解决方案:

    获取系统服务getSystemService时使用ApplicationContext
    context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);

    java.lang.IllegalArgumentException: View not attached to window manager

    错误日志产生原因:

    在延时线程里调用了ProgressDialog.dismiss,但此时主Activity已经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();    //调用超类对应方法
             }
        }
     }
    

    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

    错误日志产生原因及解决方案:

    根据报错提示 “make sure class name exists, is public, and has an empty constructor that is public” ,若Fragement定义有带参构造函数,则一定要定义public的默认的构造函数。即可解决此问题。
    除了他说的public.还有一个就是弄个空的构造函数。
    例如我是这样定义了我的fragment。带有了构造函数

    public TestFragment(int index){
    mIndex = index;
    }
    

    然后我添加了一个这样的构造函数

    public TestFragment(){
    }
    

    java.lang.IllegalStateException: Unable to get package info for com.crfchina.market; is package not installed?

    错误日志产生原因:

    简单的卸载app 没有卸载干净,然后再次运行,当dalvik重新安装。apk文件并试图重用以前的活动从同一个包


    好了目前就总结这么多,后续还会继续更新补充!毕竟太长也没有人愿意耐下心去看,以上也是曾经遇到过坑,希望有遇到的兄弟能从中受益!欢迎大家贴一些内容作为补充,相互学习共同进步……

    相关文章

      网友评论

        本文标题:Android 常见的错误日志及相应的解决方案总结

        本文链接:https://www.haomeiwen.com/subject/ffdnkxtx.html