阅读 1007

我遇到的那些bug(持续更新中)

此文主要是收集一些自己遇到的一些问题,方便自己总结,也方便他人解决问题!目前比较少,一点一点收集吧,路是一步一步走,坑也要一个一个的踩。

1、内存泄漏

最近我负责维护的一个老项目遇到一个很奇怪的bug,测试描述说某个Activity的第一次进入正常,第二次进入点击某个按钮的时候会崩溃。检查日志是View空指针异常。我第一反应就是内存导致的,于是开始从这个方向检查。 首先通过Profiler检查,发现没有异常,然后通过LeakCanary继续检查,依然没有发现异常。最后通过方法调用的来分析。这个方法在第一次进入Activity的时候可以执行无数次,第二次进来以后第一次也是正常的,但是当执行第二次的时候就崩溃了。后来在检查这个方法的时候,发现这个方法第二次执行的时候,是通过PopupWindow点击回调来调用的,而PopupWindow只是一个简单的私有成员变量,并不是静态的成员变量。后来在PopupWindow实例化的时候发现PopupWindow通过单例的方法实例化的,从而导致第二次接口回调的时候,使用的还是上一个接口回调,而上一个接口回调指向的是已经被回收的Activity,从而导致使用该PopupWindow的Activity第二次进来使用它的时候,就崩溃了!

修改前:
private static RecommendPopup instance;
public static RecommendPopup getInstance(Activity context, IMapListener iMapListener){
    if (instance == null)
        instance = new RecommendPopup(context,iMapListener);
    return instance;
}

修改后:
public RecommendPopup(Activity context,IMapListener iMapListener) {
        super(context);
        this.mActivity = context;
        this.iMapListener = iMapListener;
    }
复制代码

解决思路: 这种问题,第一步确定初始化和调用的时候同一个对象指针id是否一致,如果不一致就去找异步,静态,单例。如果一致就去找异步或者其它逻辑谁动了它

2、Adapter刷新

前两天遇到一个奇怪的事情,Adapter刷新没有反应。具体逻辑就是给Adapter增加一个标志位,修改标志位以后刷新发现页面没有变化。添加断点检查确实进入了notifyDataSetChanged方法,后来查看源码发现是因为自身数据没有变化导致adapter没有进入convert方法。查看源码:

  /**
    * Notify any registered observers that the data set has changed.
    *
    * <p>There are two different classes of data change events, item changes and structural
    * changes. Item changes are when a single item has its data updated but no positional
    * changes have occurred. Structural changes are when items are inserted, removed or moved
    * within the data set.</p>
    *
    * <p>This event does not specify what about the data set has changed, forcing
    * any observers to assume that all existing items and structure may no longer be valid.
    * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
    *
    * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
    * for adapters that report that they have {@link #hasStableIds() stable IDs} when
    * this method is used. This can help for the purposes of animation and visual
    * object persistence but individual item views will still need to be rebound
    * and relaid out.</p>
    *
    * <p>If you are writing an adapter it will always be more efficient to use the more
    * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
    * as a last resort.</p>
    *
    * @see #notifyItemChanged(int)
    * @see #notifyItemInserted(int)
    * @see #notifyItemRemoved(int)
    * @see #notifyItemRangeChanged(int, int)
    * @see #notifyItemRangeInserted(int, int)
    * @see #notifyItemRangeRemoved(int, int)
    */
    public final void notifyDataSetChanged() {
    mObservable.notifyChanged();
    }
    
    
    
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
       ...

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
        
        ...
    }
复制代码

解决思路: 将标志位绑定到数据,而非绑定到Adapter

3、SharedPreferences数据不能保存问题

最近我负责维护的开源库UpdatePlugin有用户提了一个问题:第二次忽略版本的设置,成功以后在第二次进入应用的时候发现被忽略的版本号还停留在第一次的版本号。经过写demo测试,确认这个是SharedPreferences的锅。具体原因还在分析当中,有点烧脑。

先说一下解决方案吧,用户提供了一个解决方案:

修改前:
public static void saveIgnoreVersion(int versionCode) {
    Set<String> ignoreVersions = getIgnoreVersions();
    if (!ignoreVersions.contains(String.valueOf(versionCode))) {
        ignoreVersions.add(String.valueOf(versionCode));
        getUpdatePref().edit().putStringSet("ignoreVersions",ignoreVersions).apply();
    }
}

// 用户提供修改方法:
public static void saveIgnoreVersion(int versionCode) {
    Set<String> ignoreVersions = getIgnoreVersions();
    if (!ignoreVersions.contains(String.valueOf(versionCode))) {
        ignoreVersions.add(String.valueOf(versionCode));
        getUpdatePref().edit().clear().putStringSet("ignoreVersions",ignoreVersions).apply();
    }
}

// 最后修改方法:

/**
 * 框架内部所提供使用的一些缓存数据存取:如下载进度、忽略版本。
 * @author haoge
 */
public class UpdatePreference {

    private static final String PREF_NAME = "update_preference";

    public static List<String> getIgnoreVersions () {
        String txt =  getUpdatePref().getString("ignoreVersions", "");
        if(TextUtils.isEmpty(txt))return new ArrayList<>();
        txt = txt.replace("[","").replace("]","");
        String[] result = txt.split(",");
        // 杜绝 java.lang.UnsupportedOperationException
        return new ArrayList<>(Arrays.asList(result));
    }

    public static void saveIgnoreVersion(int versionCode) {
        List<String> ignoreVersions = getIgnoreVersions();
        if (!ignoreVersions.contains(String.valueOf(versionCode))) {
            ignoreVersions.add(String.valueOf(versionCode));
            getUpdatePref().edit().putString("ignoreVersions",ignoreVersions.toString()).apply();
        }
    }

    private static SharedPreferences getUpdatePref () {
        return ActivityManager.get().getApplicationContext().getSharedPreferences(PREF_NAME,Context.MODE_PRIVATE);
    }
}
复制代码

选择修改存储方式是因为putStringSet目前的bug暂时还没有分析出具体原因,所以暂时先排除此方法,避免再次发生未知错误。

疑惑: 仔细看了源码关于保存的commitToMemorywriteToFile多遍,始终不明白为什么更新原有的Set<String>以后,再次进入应用的时候数据会丢失?网上的解决方法基本上都是围绕commitToMemory方法来分析,将mcr.changesMade设置为默认的false来避免后期被写入文件,但是如果通过修改mcr.changesMade来避免数据写入文件的话,则mMap无法保存需要存入的值,那么在存成功以后(还没有退出应用期间)是如何取到正确的值呢?

这个问题一直困扰着我,哪位大佬有思路请指点一下,谢谢?

文档提醒:

Note that you must not modify the set instance returned by this call. The consistency of the stored data is not guaranteed if you do, nor is your ability to modify the instance at all.

参考文章:

putStringSet数据不能保存问题

4、GlideApp 无法自动生成

最近复制同事其它项目的一个工具类到Kotlin新项目,发现一个问题:GlideApp无法自动生成,于是经过以下步骤实现:

  1. 每个Model下的gradle均需配置apt插件
apply plugin:'kotlin-kapt'
复制代码
  1. 通过Gradle依赖Glide,并且将注解依赖到每一个使用GlideAppModule
implementation 'com.github.bumptech.glide:glide:4.10.0'
// 使用GlideApp 的地方都需要添加
kapt 'com.github.bumptech.glide:compiler:4.10.0'
复制代码
  1. 在每一个Model下的gradle文件添加一下内容:
 android{
      compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }
 } 
复制代码
  1. 然后先同步sync now,再重新编译Rebuild Project即可

注意 由于gradle环境问题,不能保证每个人都能通过上述步骤解决依赖问题,只能是一个待校验的方案。

5、自定义View生成失败

是这样的,今天我本来打算自定义一个View,然后写好了之后加载直接崩溃了,我检查了一下代码:

class ProgressView (context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attributeSet, defStyleAttr) {
    init{
        ...
    }
   ...
}
复制代码

异常日志:

Caused by: android.view.InflateException: Binary XML file line #15: Binary XML file line #15: Error inflating class com.junerver.videorecorder.ProgressView
     Caused by: android.view.InflateException: Binary XML file line #15: Error inflating class com.junerver.videorecorder.ProgressView
     Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
复制代码

我一看到init关键字,以为是我的初始化有什么问题,检查了代码无果之后,打开了百度,得到这么一个结论:

网络结果

看到这里我有点不信邪,于是把Kotlinjava代码一看,好吧,我服气了!

知错能改善莫大焉,意思就是晓得哪里错了改起来就容易了!根据之前的Kotlin实战填坑#构造方法的关键字经验,我将代码修改如下:

class ProgressView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) : View(context, attributeSet, defStyleAttr) {
    init{
        ...
    }
   ...
}
复制代码

结果错误依然无动于衷,还是嚣张的爆红着!还好我还知道一个招式:

class ProgressView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attributeSet, defStyleAttr) {
    init{
        ...
    }
   ...
}
复制代码

之所以之前增加了JvmOverloads注解还是失败,是因为没有对可空的字段设置默认值,在没有默认值的情况下,所以还是只有一种构造方法。这下终于解决了!

6、AndroidStudio升级到3.6异常

最近看见QQ群很多人都把AndroidStudio升级到3.6正式版看,我看见挺新颖的,于是趁着刚开工不是很忙的时候,就直接更新了,然后就遇到了以下问题:

C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\126332c53653ff7049fa8c511808692d\constraintlayout-2.0.0-beta1\res\values\values.xml:255:5-4197: AAPT: error: resource attr/flow_horizontalSeparator (aka com.cn.dcjt.firelibrary:attr/flow_horizontalSeparator) not found.
复制代码

(1)尝试在gradle.propertiesaapt注解关闭,失败

android.enableAapt2=false
复制代码

(2)尝试检查项目中所有Moduleconstraintlayout版本号,发现版本号完全相同,不存在差异

(3)尝试检查项目中的flow_horizontalSeparator属性,无发现。甚至将项目中所有的constraintlayout布局修改为相对布局,依然无效。

(4)最后将constraintlayout的版本号由beta版本修改为正式版本,解决!

// 修改前
"androidx.constraintlayout:constraintlayout:2.0.0-beta1"
// 修改后
"androidx.constraintlayout:constraintlayout:1.1.3"
复制代码

7、发现有多个文件具有与操作系统无关的路径

错误日志:

More than one file was found with OS independent path 'META-INF/library_release.kotlin_module'
复制代码

经过百度后看到

最近在引入两个kotlin写的aar库时编译器报了这样的一个错误。说是打包时存在两个相同的文件,文件路径是META-INF/library_release.kotlin_module

后来经过检查,发现是com.github.lygttpod:SuperTextView:2.4.2com.github.chenBingX:SuperTextView:v3.2.5.99两套框架冲突了!知道原因后,解决就简单了:

  • 两个框架二选一
  • 在项目的主Module即打包的壳组件(一般是app)增加如写代码:
android {
    packagingOptions {
       exclude 'META-INF/*.kotlin_module'
   }
}
复制代码

参考:

blog.csdn.net/yinxing2008…

deskid.github.io/2019/03/05/…


后记

1----本文由苏灿烤鱼整理,将不定期更新。

2----如果有什么想要交流的,欢迎留言。也可以加微信:Vicent_0310

3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正

关注下面的标签,发现更多相似文章
评论