Android 开发中遇到的 bug(11)

前言

记录开发中遇到的 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 <androidx.recyclerview.widget.RecyclerView app:items> that accepts parameter type 'androidx.lifecycle.LiveData<java.util.List<com.readbook.chinesepoetry.data.model.PoetryBean>>'

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<RecommendListBean> 这个非空类型时报错的。
解决办法:
把代码改为:

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.<init>(SQLiteProgram.java:58)
        at android.database.sqlite.SQLiteStatement.<init>(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

问题描述:

看下面的设计图,

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

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

解决办法:

拷贝 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探究

最后

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willwaywang6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值