Android 开发中遇到的 bug(11)

目录

  • 前言
  • 正文
    • 1. If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.
    • 2. 在 RecyclerView.Adapter 的 onBindViewHolder 方法中使用 Databinding,导致列表条目重复
    • 3. kotlin.TypeCastException: null cannot be cast to non-null type Response
    • 4. Java:Inner class cannot have static declaration
    • 5. javax.net.ssl.SSLHandshakeException(Chain validation failed)
    • 6. android.database.sqlite.SQLiteException: Cannot add a NOT NULL column with default value NULL (code 1): , while compiling: ALTER TABLE poetry_detail ADD COLUMN collected INTEGER NOT NULL
    • 7. java.lang.IllegalArgumentException: Form-encoded method must contain at least one @Field.
    • 8. Kotlin 中的内部类如何获取外部类的实例?
    • 9. 如何去掉 RecyclerView 列表布局指定位置的分割线?
    • 10. Android安装后第一次运行切换到后台无法恢复
  • 最后

前言

记录开发中遇到的 bug,不再让自己重复地被同样的 bug 折磨。

正文

1. If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.

时间:2019年12月29日12:04:20
问题描述:
在 kotlin 工程中,使用 Databinding 的 @BindingAdapter 报错:

Cannot find a setter for  that accepts parameter type 'androidx.lifecycle.LiveData>'

If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.

问题解决:
在 build.gradle 文件中添加:

apply plugin: 'kotlin-kapt'

2. 在 RecyclerView.Adapter 的 onBindViewHolder 方法中使用 Databinding,导致列表条目重复

时间:2019年12月29日16:40:29
问题描述:

class RecommendAdapter(private val viewModel: RecommendViewModel) :
        ListAdapter<PoetryBean, RecommendViewHolder>(PoetryBeanDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecommendViewHolder {
        val binding = RecommendRecycleItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return RecommendViewHolder(binding)
    }


    override fun onBindViewHolder(holder: RecommendViewHolder, position: Int) {
        val item = getItem(position)
        holder.bindItem(item)
    }

    class RecommendViewHolder(private val binding: RecommendRecycleItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bindItem(item: PoetryBean) {
            Timber.d("item=$item")
            with(item) {
                binding.tvName.text = name + Integer.toString(id)
                binding.tvDynastyPoet.text = itemView.context.getString(R.string.common_dynasty_poet, dynasty, poet)
                binding.tvContent.text = Html.fromHtml(content)
            }
        }
    }
}

问题分析:
对比查看 android-architecture 的代码,发现会在 bindItem() 方法中最后一行再调用一次:

binding.executePendingBindings()

增加这一行代码后,解决了问题。但还是要再详细了解一下。
再去查看一下官方文档的说明:https://developer.android.google.cn/topic/libraries/data-binding/generated-binding#immediate_binding

When a variable or observable object changes, the binding is scheduled to change before the next frame. There are times, however, when binding must be executed immediately. To force execution, use the executePendingBindings() method.

并且官方也给了实例代码 https://developer.android.google.cn/topic/libraries/data-binding/generated-binding#dynamic_variables :

override fun onBindViewHolder(holder: BindingHolder, position: Int) {
    item: T = items.get(position)
    holder.binding.setVariable(BR.item, item);
    holder.binding.executePendingBindings();
}

参考:https://stackoverflow.com/questions/53043412/android-why-use-executependingbindings-in-recyclerview

3. kotlin.TypeCastException: null cannot be cast to non-null type Response

时间:2020年1月5日21:30:35
问题描述:

object CacheUtil {
    private val cache = ACache.get(File(Utils.getApp().filesDir, "ACache"))
    private const val RECOMMEND_LIST = "recommend_list"

    fun getRecommendList(page: Int): Response<RecommendListBean>? {
        return cache.getAsObject(RECOMMEND_LIST + page) as Response<RecommendListBean>
    }
}

第一次取出 cache.getAsObject(RECOMMEND_LIST + page)null,转为 Response 这个非空类型时报错的。
解决办法:
把代码改为:

return cache.getAsObject(RECOMMEND_LIST + page) as Response<RecommendListBean>?

4. Java:Inner class cannot have static declaration

时间:2020年1月11日13:17:22
问题描述:

public class Apply {
     class  ApplyTest {
     	// 下面的方法编译报错:Inner class cannot have static declaration
        public static void main(String[] args) throws Exception {
            List<Shape> shapes = new ArrayList<>();
        }
    }
}

解决办法:

public class Apply {
     static class ApplyTest {
     	// 下面的方法编译报错:Inner class cannot have static declaration
        public static void main(String[] args) throws Exception {
            List<Shape> shapes = new ArrayList<>();
        }
    }
}

5. javax.net.ssl.SSLHandshakeException(Chain validation failed)

时间:2020年1月19日10:13:44
问题描述:
我在应用里面使用 Glide 加载 github 上存储的图片资源,可是在一个手机上原来加载出来,后来却加载不出来了。

2020-01-29 09:57:22.392 9043-9043/com.readbook.chinesepoetry W/Glide: Load failed for https://raw.githubusercontent.com/xxx/poetry/master/image/image_248.jpg with size [192x192]
    class com.bumptech.glide.load.engine.GlideException: Failed to load resource
    There was 1 cause:
    javax.net.ssl.SSLHandshakeException(Chain validation failed)
     call GlideException#logRootCauses(String) for more detail
      Cause (1 of 1): class com.bumptech.glide.load.engine.GlideException: Fetching data failed, class java.io.InputStream, REMOTE
    There was 1 cause:
    javax.net.ssl.SSLHandshakeException(Chain validation failed)
     call GlideException#logRootCauses(String) for more detail
        Cause (1 of 1): class com.bumptech.glide.load.engine.GlideException: Fetch failed
    There was 1 cause:
    javax.net.ssl.SSLHandshakeException(Chain validation failed)
     call GlideException#logRootCauses(String) for more detail
          Cause (1 of 1): class javax.net.ssl.SSLHandshakeException: Chain validation failed
2020-01-29 09:57:22.396 9043-9043/com.readbook.chinesepoetry I/Glide: Root cause (1 of 1)
    javax.net.ssl.SSLHandshakeException: Chain validation failed
        at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:355)
        at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:192)
        at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:149)
        at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:112)
        at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:184)
        at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:126)
        at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:95)
        at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:281)
        at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:224)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:461)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:127)
        at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:89)
        at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(Unknown Source:0)
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadDataWithRedirects(HttpUrlFetcher.java:100)
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:56)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.startNextOrFail(MultiModelLoader.java:164)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.onLoadFailed(MultiModelLoader.java:154)
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:62)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100)
        at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:63)
        at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:310)
        at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279)
        at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:234)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
        at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:431)
     Caused by: java.security.cert.CertificateException: Chain validation failed
        at com.android.org.conscrypt.TrustManagerImpl.verifyChain(TrustManagerImpl.java:705)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:537)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:558)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:626)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:493)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:416)
        at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:337)
        at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
        at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)
        at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:203)
        at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:592)
        at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
        at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:351)
        at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:192) 
        at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:149) 
        at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:112) 
        at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:184) 
        at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:126) 
        at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:95) 
        at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:281) 
        at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:224) 
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:461) 
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:127) 
        at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:89) 
        at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(Unknown Source:0) 
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadDataWithRedirects(HttpUrlFetcher.java:100) 
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:56) 
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100) 
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.startNextOrFail(MultiModelLoader.java:164) 
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.onLoadFailed(MultiModelLoader.java:154) 
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:62) 
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100) 
        at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:63) 
        at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:310) 
        at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279) 
        at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:234) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
        at java.lang.Thread.run(Thread.java:764) 
        at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:431) 
     Caused by: java.security.cert.CertPathValidatorException: Response is unreliable: its validity interval is out-of-date
2020-01-29 09:57:22.399 9043-9043/com.readbook.chinesepoetry I/Glide:     at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:133)
        at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:225)
        at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:143)
        at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:79)
        at java.security.cert.CertPathValidator.validate(CertPathValidator.java:301)
        at com.android.org.conscrypt.TrustManagerImpl.verifyChain(TrustManagerImpl.java:701)
        	... 39 more
     Caused by: java.security.cert.CertPathValidatorException: Response is unreliable: its validity interval is out-of-date
        at sun.security.provider.certpath.OCSPResponse.verify(OCSPResponse.java:619)
        at sun.security.provider.certpath.RevocationChecker.checkOCSP(RevocationChecker.java:709)
        at sun.security.provider.certpath.RevocationChecker.check(RevocationChecker.java:363)
        at sun.security.provider.certpath.RevocationChecker.check(RevocationChecker.java:337)
        at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:125)
        	... 44 more
    	Suppressed: java.security.cert.CertPathValidatorException: Could not determine revocation status
        at sun.security.provider.certpath.RevocationChecker.buildToNewKey(RevocationChecker.java:1092)
        at sun.security.provider.certpath.RevocationChecker.verifyWithSeparateSigningKey(RevocationChecker.java:910)
        at sun.security.provider.certpath.RevocationChecker.checkCRLs(RevocationChecker.java:577)
        at sun.security.provider.certpath.RevocationChecker.checkCRLs(RevocationChecker.java:465)
        at sun.security.provider.certpath.RevocationChecker.check(RevocationChecker.java:394)
        		... 46 more

解决办法:原来是手机时间的问题,修改为当前时间,解决了这个问题。

6. android.database.sqlite.SQLiteException: Cannot add a NOT NULL column with default value NULL (code 1): , while compiling: ALTER TABLE poetry_detail ADD COLUMN collected INTEGER NOT NULL

时间:2020年04月03日17:11:01
使用的是谷歌的 Room。
问题描述:

 E/SQLiteLog: (1) Cannot add a NOT NULL column with default value NULL
 E/DetailViewModel$loadDetail: android.database.sqlite.SQLiteException: Cannot add a NOT NULL column with default value NULL (code 1): , while compiling: ALTER TABLE poetry_detail ADD COLUMN collected INTEGER NOT NULL
        at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
        at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:905)
        at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:516)
        at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
        at android.database.sqlite.SQLiteProgram.(SQLiteProgram.java:58)
        at android.database.sqlite.SQLiteStatement.(SQLiteStatement.java:31)
        at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1705)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1636)
        at androidx.sqlite.db.framework.FrameworkSQLiteDatabase.execSQL(FrameworkSQLiteDatabase.java:242)
        at c.PoetryDatabase$Companion$MIGRATION_3_4$1.migrate(PoetryDatabase.kt:81)

在原来的类中

@Entity(tableName = "poetry_detail")
data class PoetryDetailBean(
        val about: String = "",
        val content: String = "",
        val dynasty: String = "",
        val fanyi: String = "",
        @PrimaryKey
        val id: Int = 0,
        val name: String = "",
        val poet: String = "",
        val poetcontent: String = "",
        val poetdesc: String = "",
        val poetimage: String = "",
        val shangxi: String = "",
        val tags: List<String> = emptyList(),
)

添加一个字段:

 var collected: Boolean = false,

然后做数据库的迁移:

private val MIGRATION_3_4 = object : Migration(3, 4) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE poetry_detail "
        + "ADD COLUMN collected INTEGER NOT NULL")
    }
}

在手机上先安装旧版本,再安装新版本后运行报错。
参考:“Cannot add a NOT NULL column with default value NULL” with Requery after automatic DB migration

解决办法:
仔细看一下报错日志:

Cannot add a NOT NULL column with default value NULL (code 1): , while compiling: ALTER TABLE poetry_detail ADD COLUMN collected
当编译 ALTER TABLE poetry_detail ADD COLUMN collected 这条语句时,不能把一个默认的 NULL 值添加给一个NOT NULL 的列。

我们通过明确地指明一个不是 NULL 的默认值。这里使用 SQL 中的 DEFAULT 约束。
DEFAULT 约束用于向列中插入默认值。

private val MIGRATION_3_4 = object : Migration(3, 4) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE poetry_detail "
        + "ADD COLUMN collected INTEGER DEFAULT 0 NOT NULL")
    }
}

可以看到,只是在执行的 sql 语句里加上了 DEFAULT 0

7. java.lang.IllegalArgumentException: Form-encoded method must contain at least one @Field.

时间:2020年04月03日17:51:52
问题描述:

E/RecommendViewModel$loadRefresh: java.lang.IllegalArgumentException: Form-encoded method must contain at least one @Field.
        for method ApiService.getBannerList
        at retrofit2.Utils.methodError(Utils.java:52)
        at retrofit2.Utils.methodError(Utils.java:42)
        at retrofit2.RequestFactory$Builder.build(RequestFactory.java:203)
        at retrofit2.RequestFactory.parseAnnotations(RequestFactory.java:67)
        at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:26)
        at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
        at retrofit2.Retrofit$1.invoke(Retrofit.java:149)
        at java.lang.reflect.Proxy.invoke(Proxy.java:813)
        at $Proxy6.getBannerList(Unknown Source)
        at com.readbook.chinesepoetry.data.source.remote.RecommendRemoteDataSource.getBannerList(RecommendRemoteDataSource.kt:20)

解决办法:
查看日志,定位到代码:

interface ApiService {
    @FormUrlEncoded
    @POST("/shishuzhonghua/banners.php")
    fun getBannerList(): Observable<Response<BannerListBean>>
}

修改为:

interface ApiService {
    @POST("/shishuzhonghua/banners.php")
    fun getBannerList(): Observable<Response<BannerListBean>>
}

8. Kotlin 中的内部类如何获取外部类的实例?

时间:2020年04月03日19:13:17
我们知道在 Java 中的写法是这样的:

public class Outer {
    class Inner {
        public Outer getOuterReference() {
            return Outer.this;
        }
    }
}

对应在 Kotlin 中的写法是:

class Outer {
    inner class Inner {
        fun getOuterReference(): Outer = this@Outer
    }
}

下面举一个开发中的实际应用:

class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val button = Button(this)
        button.setOnClickListener { 
            Toast.makeText(this@MyActivity, "click me", Toast.LENGTH_SHORT).show()
        }
    }
}

9. 如何去掉 RecyclerView 列表布局指定位置的分割线?

时间:2020年04月04日14:00:10

问题描述:

看下面的设计图,
Android 开发中遇到的 bug(11)_第1张图片

要求 banner 和热门诗词,热门诗词和诗歌条目之间没有分割线。

但是,我实现的确实有的,如下图所示:

Android 开发中遇到的 bug(11)_第2张图片

解决办法:

拷贝 androidx.recyclerview.widget.DividerItemDecoration 这个源码类到自己的工程里,修改 drawVertical 方法为:

    private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int left;
        final int right;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
        	// modified codes start
            int pos = parent.getChildLayoutPosition(parent.getChildAt(i));
            Timber.d("drawVertical: i = %d, pos = %d", i, pos);
            // don't draw the first two divider
            if (pos <= 1) {
                continue;
            }
            // modified codes end
            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

在代码里使用修改过的DividerItemDecoration

val decoration = DividerItemDecoration(activity, DividerItemDecoration.VERTICAL)
            ContextCompat.getDrawable(activity, R.drawable.recommend_item_divider)?.let {
                decoration.setDrawable(it)
            }
recyclerView.addItemDecoration(decoration)

运行后,符合了 UI 的设计需求。

关于改动源码的地方,需要特别说一下,网上有不少说法是:

 // don't draw the first two divider
 if (i <= 1) {
     continue;
 }

这种是错误的,因为 parent.getChildCount() 获取的是屏幕上显示的条目数,并非所有的条目数。
可以看我打印的日志:

D/DividerItemDecoration: drawVertical: i = 0, pos = 0
D/DividerItemDecoration: drawVertical: i = 1, pos = 1
D/DividerItemDecoration: drawVertical: i = 2, pos = 2
D/DividerItemDecoration: drawVertical: i = 3, pos = 3
D/DividerItemDecoration: drawVertical: i = 4, pos = 4
D/DividerItemDecoration: drawVertical: i = 5, pos = 5
D/DividerItemDecoration: drawVertical: i = 0, pos = 2
D/DividerItemDecoration: drawVertical: i = 1, pos = 3
D/DividerItemDecoration: drawVertical: i = 2, pos = 4
D/DividerItemDecoration: drawVertical: i = 3, pos = 5
D/DividerItemDecoration: drawVertical: i = 4, pos = 6
D/DividerItemDecoration: drawVertical: i = 0, pos = 3
D/DividerItemDecoration: drawVertical: i = 1, pos = 4
D/DividerItemDecoration: drawVertical: i = 2, pos = 5
D/DividerItemDecoration: drawVertical: i = 3, pos = 6
D/DividerItemDecoration: drawVertical: i = 4, pos = 7

可以看到 i 的值最多就是 5,这时屏幕上显示着 6 个条目;而 int pos = parent.getChildLayoutPosition(parent.getChildAt(i)); 才真正是条目的索引。

如果按这种写法,那么当滑动到下一屏时,头两个条目也是没有分割线的。
可以看我录制的动图:

但是,如果需求另外变化了,比如不需要指定位置(比如第15个到第20个)的分割线,不需要最后两个条目的分割线,我这种方法就不适用了。怎么办呢?

这里推荐一个 github 上开源的 RecyclerView-FlexibleDivider,可以完美解决这些需求。

需要用到的是 FlexibleDividerDecoration 类中的:

    public interface VisibilityProvider {

        /**
         * Returns true if divider should be hidden.
         *
         * @param position Divider position (or group index for GridLayoutManager)
         * @param parent   RecyclerView
         * @return True if the divider at position should be hidden
         */
        boolean shouldHideDivider(int position, RecyclerView parent);
    }

更多的用法,可以去查看 github 上提供的 demo。

10. Android安装后第一次运行切换到后台无法恢复

时间:2020年04月04日16:15:07
问题描述:
release 包的情况下,应用里有个 MainActivity,这是设置了

  <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
   intent-filter>

现在在 MainActivity 里点击按钮,打开 SecondActivity

然后,按 home 键,应用退入后台,显示桌面。
在桌面上,点击 icon,本来应该显示的是 SecondActivity,但实际上显示的是 MainActivity

解决办法:
添加一个 SplashActivity 闪屏页,设置:

  <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
   intent-filter>

在它的 onCreate() 方法里,添加以下代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
        finish();
        return;
    }
    MainActivity.start(this);
}

参考:
Android安装后第一次运行切换到后台无法恢复;
第一次安装app后从桌面进入,app会重启的bug探究。

最后

代码出错了,关键是要仔细查看日志。能够仔细地查看日志,就离解决问题很近了。

你可能感兴趣的:(Android,开发中遇到的,bug,集锦)