<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Android开发归档 - 日志</title>
	<atom:link href="https://www.log.show/tag/android%E5%BC%80%E5%8F%91/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.log.show/tag/android开发/</link>
	<description>LOG.SHOW</description>
	<lastBuildDate>Sat, 27 Dec 2025 10:54:49 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://www.log.show/wp-content/uploads/2025/11/cropped-logo-32x32.png</url>
	<title>Android开发归档 - 日志</title>
	<link>https://www.log.show/tag/android开发/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Android ViewPager2嵌套滑动冲突</title>
		<link>https://www.log.show/log/android-viewpager2-nested-problem/</link>
					<comments>https://www.log.show/log/android-viewpager2-nested-problem/#respond</comments>
		
		<dc:creator><![CDATA[LOGGER]]></dc:creator>
		<pubDate>Sat, 27 Dec 2025 10:54:19 +0000</pubDate>
				<category><![CDATA[LOG]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Android开发]]></category>
		<category><![CDATA[ViewPager2]]></category>
		<category><![CDATA[嵌套滑动]]></category>
		<category><![CDATA[嵌套问题]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[滑动冲突]]></category>
		<guid isPermaLink="false">https://log.show/?p=484</guid>

					<description><![CDATA[<p>当你使用两个ViewPager2嵌套时，确实是会有恶心的滑动冲突。默认是优先响应父布局的ViewPager2的 [&#8230;]</p>
<p><a href="https://www.log.show/log/android-viewpager2-nested-problem/">Android ViewPager2嵌套滑动冲突</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">当你使用两个ViewPager2嵌套时，确实是会有恶心的滑动冲突。默认是优先响应父布局的ViewPager2的。</p>



<p class="wp-block-paragraph">而如果你希望它优先响应子布局，当子布局滑动到最后一格的时候才响应父布局，下面是我的解决方案：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">以下内容，我们假设父布局的ViewPager2存在A,B,C,D四个页面，A页面存在子ViewPager2</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">首先我是用的广播来触发的：</p>



<ol class="wp-block-list">
<li>默认关闭父布局滑动</li>



<li>子布局滑动到最后一格，发送广播通知父布局打开滑动</li>



<li>父布局切换A，关闭父布局滑动</li>
</ol>



<p class="wp-block-paragraph">但是问题很明显，我从B,C,D重新滑到A页面，父布局是已经被通知关闭滑动的，我没办法再次滑动返回B,C,D页面。所以我采用了下面的Google提供的方案。</p>



<h3 class="wp-block-heading">首先新建一个工具 NestedScrollableHost</h3>



<pre class="wp-block-code"><code>
class NestedScrollableHost @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {

    private var touchSlop = ViewConfiguration.get(context).scaledTouchSlop
    private var initialX = 0f
    private var initialY = 0f

    // 找到直接子节点中的 ViewPager2
    private val parentViewPager: ViewPager2?
        get() {
            var v: View? = parent as? View
            while (v != null &amp;&amp; v !is ViewPager2) {
                v = v.parent as? View
            }
            return v as? ViewPager2
        }

    private val child: View? get() = if (childCount &gt; 0) getChildAt(0) else null

    private fun canChildScroll(orientation: Int, delta: Float): Boolean {
        val direction = -delta.sign.toInt()
        return when (orientation) {
            ViewPager2.ORIENTATION_HORIZONTAL -&gt; child?.canScrollHorizontally(direction) ?: false
            ViewPager2.ORIENTATION_VERTICAL -&gt; child?.canScrollVertically(direction) ?: false
            else -&gt; throw IllegalArgumentException()
        }
    }

    override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
        handleInterceptTouchEvent(e)
        return super.onInterceptTouchEvent(e)
    }

    private fun handleInterceptTouchEvent(e: MotionEvent) {
        val orientation = parentViewPager?.orientation ?: return

        // 只有当子 View 能够滑动时，才处理拦截逻辑
        if (!canChildScroll(orientation, -1f) &amp;&amp; !canChildScroll(orientation, 1f)) {
            return
        }

        if (e.action == MotionEvent.ACTION_DOWN) {
            initialX = e.x
            initialY = e.y
            parent.requestDisallowInterceptTouchEvent(true)
        } else if (e.action == MotionEvent.ACTION_MOVE) {
            val dx = e.x - initialX
            val dy = e.y - initialY
            val isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL

            val scaledDx = dx.absoluteValue * if (isVpHorizontal) 1f else .5f
            val scaledDy = dy.absoluteValue * if (isVpHorizontal) .5f else 1f

            if (scaledDx &gt; touchSlop || scaledDy &gt; touchSlop) {
                if (isVpHorizontal == (scaledDy &gt; scaledDx)) {
                    // 滑动方向与 ViewPager2 方向不一致，允许父级拦截
                    parent.requestDisallowInterceptTouchEvent(false)
                } else {
                    // 滑动方向一致，判断子 View 是否能继续滑动
                    if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
                        parent.requestDisallowInterceptTouchEvent(true)
                    } else {
                        parent.requestDisallowInterceptTouchEvent(false)
                    }
                }
            }
        }
    }
}</code></pre>



<h3 class="wp-block-heading">在子布局外嵌套这个View</h3>



<p class="wp-block-paragraph">在默认情况下，你嵌套之后发现父布局的滑动不会被触发，子布局正常了。这个问题的原因很简单，解决方案↓</p>



<h3 class="wp-block-heading">解决滑动监听的问题</h3>



<p class="wp-block-paragraph">ViewPager2实际上是一个RecyclerView，因此它默认打开了越界回弹（OverScroll）。就是那种滑动到边缘的拉伸效果（早期Android版本可能是一个奇怪的发光效果）。当这个效果触发时，系统会认为内层 View 已经处理了这次滑动，从而不会把剩余的位移传递给父布局。</p>



<p class="wp-block-paragraph">所以你需要禁用这个属性：</p>



<pre class="wp-block-code"><code>// 在代码中找到内层的 ViewPager2
val innerViewPager = findViewById&lt;ViewPager2&gt;(R.id.inner_view_pager)

// ViewPager2 内部是一个 RecyclerView，需要找到并设置它
(innerViewPager.getChildAt(0) as? RecyclerView)?.overScrollMode = View.OVER_SCROLL_NEVER</code></pre>



<p class="wp-block-paragraph">当然也有其它的可能，比如你的NestedScrollableHost嵌套层级太深。你需要保证你的NestedScrollableHost下一层就是你的Viewpager2的子视图！</p>



<h3 class="wp-block-heading">其他思路：</h3>



<p class="wp-block-paragraph">这里有一个更粗暴，更清晰的方案，但是可能会有奇怪的问题，不过我认为可以记录下来(与我之前的想法比较接近，可能会有奇怪的问题)：</p>



<pre class="wp-block-code"><code>innerViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
        val isAtEnd = position == (innerViewPager.adapter?.itemCount ?: 0) - 1
        val isAtStart = position == 0
        
        if ((isAtEnd &amp;&amp; positionOffsetPixels == 0) || (isAtStart &amp;&amp; positionOffsetPixels == 0)) {
            // 到达边界，允许父类拦截
            innerViewPager.parent.requestDisallowInterceptTouchEvent(false)
        } else {
            // 未到边界，禁止父类拦截
            innerViewPager.parent.requestDisallowInterceptTouchEvent(true)
        }
    }
})</code></pre>
<p><a href="https://www.log.show/log/android-viewpager2-nested-problem/">Android ViewPager2嵌套滑动冲突</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.log.show/log/android-viewpager2-nested-problem/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Android开发中屏蔽第三方库的权限</title>
		<link>https://www.log.show/log/android-prerelease-permission-check/</link>
					<comments>https://www.log.show/log/android-prerelease-permission-check/#respond</comments>
		
		<dc:creator><![CDATA[LOGGER]]></dc:creator>
		<pubDate>Fri, 19 Dec 2025 05:55:33 +0000</pubDate>
				<category><![CDATA[LOG]]></category>
		<category><![CDATA[AndroidManifest]]></category>
		<category><![CDATA[Android开发]]></category>
		<category><![CDATA[APK反编译]]></category>
		<category><![CDATA[BOOT_COMPLETED]]></category>
		<category><![CDATA[tools:node="remove"]]></category>
		<category><![CDATA[TrueTime]]></category>
		<category><![CDATA[上架审核]]></category>
		<category><![CDATA[权限屏蔽]]></category>
		<category><![CDATA[第三方库权限]]></category>
		<category><![CDATA[自启动权限]]></category>
		<guid isPermaLink="false">https://log.show/?p=474</guid>

					<description><![CDATA[<p>下面是实际应用中的例子。 因为需要准确时间，所以导入了TrueTime的第三方库，而这个库会声明一个自启动权限 [&#8230;]</p>
<p><a href="https://www.log.show/log/android-prerelease-permission-check/">Android开发中屏蔽第三方库的权限</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">下面是实际应用中的例子。</p>



<p class="wp-block-paragraph">因为需要准确时间，所以导入了TrueTime的第三方库，而这个库会声明一个自启动权限<code>BOOT_COMPLETED</code>，来自于<code>AndroidManifest.xml: com.instacart.library.truetime.BootCompletedBroadcastReceiver</code></p>



<figure class="wp-block-embed is-type-wp-embed"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="i3xWcqJdHW"><a href="https://log.show/log/use-ntp-server-time-on-mobile-device/">在移动设备上使用准确的时间</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="《 在移动设备上使用准确的时间 》—日志" src="https://log.show/log/use-ntp-server-time-on-mobile-device/embed/#?secret=lNXmZW7TWu#?secret=i3xWcqJdHW" data-secret="i3xWcqJdHW" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p class="wp-block-paragraph">第一次看到我没在意，我删除了自己APP中的这个权限。</p>



<p class="wp-block-paragraph">再次被上架驳回后我发现这是第三方库的权限。在解决这个问题之前，或者说我们上架之前是需要先自查的，下面是自查方案：</p>



<ol class="wp-block-list">
<li>使用apktool反编译后查看打包之后的<code>AndroidManifest.xml</code>文件内是否存在敏感权限</li>
</ol>



<pre class="wp-block-code"><code># 反编译APK到指定目录
apktool d your_app.apk -o apk_decompile

# 查看合并后的AndroidManifest.xml
cat apk_decompile/AndroidManifest.xml | grep -i "BOOT_COMPLETED"</code></pre>



<ol start="2" class="wp-block-list">
<li>在源码中排除第三方库的该声明</li>
</ol>



<pre class="wp-block-code"><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"  &lt;!-- 必须添加这个命名空间 --&gt;
    package="com.your.package.name"&gt;

    &lt;!-- 1. 移除开机自启权限（如果有的话） --&gt;
    &lt;uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" 
                     tools:node="remove"/&gt;

    &lt;application&gt;

    &lt;/application&gt;

&lt;/manifest&gt;</code></pre>



<ol start="3" class="wp-block-list">
<li>Gradle 构建时排查依赖</li>
</ol>



<pre class="wp-block-code"><code># 查看所有依赖（Android项目）
./gradlew app:dependencies --configuration releaseRuntimeClasspath

# 结合grep筛选（Mac/Linux）
./gradlew app:dependencies | grep -i "boot"</code></pre>



<ol start="4" class="wp-block-list">
<li>我使用的方案，也是最简单的方案：使用jadx反编译后查看配置文件中的权限</li>
</ol>



<h2 class="wp-block-heading">下面是解决这个问题的方案：</h2>



<h3 class="wp-block-heading">1. 删除第三方库</h3>



<p class="wp-block-paragraph">简单见效快</p>



<h3 class="wp-block-heading">2. 移出权限</h3>



<pre class="wp-block-code"><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"  &lt;!-- 必须添加这个命名空间 --&gt;
    package="com.miaoyuyin.miao"&gt;

    &lt;!-- 保留你原有所有配置，仅添加以下移除声明 --&gt;

    &lt;!-- 1. 移除开机自启权限（如果第三方库声明了） --&gt;
    &lt;uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" 
                     tools:node="remove"/&gt;

    &lt;application
        &lt;!-- 保留你原有所有application属性和子组件 --&gt;
        &gt;

        &lt;!-- 2. 精准移除TrueTime库的开机广播接收器 --&gt;
        &lt;receiver
            android:name="com.instacart.library.truetime.BootCompletedBroadcastReceiver"
            tools:node="remove" /&gt;

        &lt;!-- 你原有所有Activity/Service/Provider等组件 --&gt;

    &lt;/application&gt;
&lt;/manifest&gt;</code></pre>



<p class="wp-block-paragraph">在修改之后，再次提交之前，你可以使用上面的自查方案再次验证是否已经修改完善！</p>
<p><a href="https://www.log.show/log/android-prerelease-permission-check/">Android开发中屏蔽第三方库的权限</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.log.show/log/android-prerelease-permission-check/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>一个Android的Log类</title>
		<link>https://www.log.show/log/android-log-utils-wrapper/</link>
					<comments>https://www.log.show/log/android-log-utils-wrapper/#respond</comments>
		
		<dc:creator><![CDATA[LOGGER]]></dc:creator>
		<pubDate>Wed, 17 Dec 2025 05:16:22 +0000</pubDate>
				<category><![CDATA[LOG]]></category>
		<category><![CDATA[Android开发]]></category>
		<category><![CDATA[Debug日志]]></category>
		<category><![CDATA[Kotlin封装]]></category>
		<category><![CDATA[Logger库]]></category>
		<category><![CDATA[LogUtils]]></category>
		<category><![CDATA[Release禁用]]></category>
		<category><![CDATA[内存泄漏]]></category>
		<category><![CDATA[异常堆栈]]></category>
		<category><![CDATA[日志工具类]]></category>
		<category><![CDATA[格式化日志]]></category>
		<guid isPermaLink="false">https://log.show/?p=468</guid>

					<description><![CDATA[<p>优点： 下面是类的内容： 在使用之前，需要在Application 中调用： 使用示例 最终可能的输出效果：  [&#8230;]</p>
<p><a href="https://www.log.show/log/android-log-utils-wrapper/">一个Android的Log类</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">优点：</p>



<ul class="wp-block-list">
<li>代码级禁用，DEBUG才有日志可以看到</li>



<li>isLoggable运行时拦截</li>



<li>零泄露风险</li>



<li>边框包裹，自动格式化</li>



<li>精准定位，点击跳转</li>



<li>使用简单，动态调用和TAG</li>



<li>零开销潜力，内存友好</li>
</ul>



<p class="wp-block-paragraph">下面是类的内容：</p>



<pre class="wp-block-code"><code>import android.util.Log
import com.orhanobut.logger.Logger
import com.orhanobut.logger.PrettyFormatStrategy
import your.package.app.BuildConfig

/**
 * 日志工具类
 * 核心规则：Release包完全禁用所有日志输出，Debug包正常输出格式化日志
 * 支持日志级别：VERBOSE/DEBUG/INFO/WARN/ERROR
 * 支持场景：无Tag日志、自定义Tag、带异常堆栈、可变参数格式化
 */
object LogUtils {
    // 全局默认Tag（可根据项目修改）
    private const val DEFAULT_TAG = "MY_APP"

    /**
     * 初始化日志配置（在App.kt的onCreate中调用）
     */
    fun init() {
        if (BuildConfig.DEBUG) {
            // Debug模式：初始化格式化日志配置
            val formatStrategy = PrettyFormatStrategy.newBuilder()
                .showThreadInfo(false)      // 显示线程信息
                .methodCount(2)            // 显示调用方法行数（2行足够定位）
                .methodOffset(1)           // 方法偏移量
                .tag(DEFAULT_TAG)          // 默认Tag
                .build()
            Logger.addLogAdapter(object : com.orhanobut.logger.AndroidLogAdapter(formatStrategy) {
                // 仅Debug模式允许日志输出
                override fun isLoggable(priority: Int, tag: String?): Boolean = BuildConfig.DEBUG
            })
        } else {
            // Release模式：移除所有日志适配器，确保无日志输出
            Logger.clearLogAdapters()
        }
    }

    // ====================== 基础日志方法（无Tag，使用默认Tag） ======================
    fun v(message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.v(message, *args)
    }

    fun d(message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.d(message, *args)
    }

    fun i(message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.i(message, *args)
    }

    fun w(message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.w(message, *args)
    }

    fun e(message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.e(message, *args)
    }

    // ====================== 带异常堆栈的日志（无Tag） ======================
    fun e(throwable: Throwable, message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.e(throwable, message, *args)
    }

    // ====================== 自定义Tag的日志方法 ======================
    fun v(tag: String, message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.t(tag).v(message, *args)
    }

    fun d(tag: String, message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.t(tag).d(message, *args)
    }

    fun i(tag: String, message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.t(tag).i(message, *args)
    }

    fun w(tag: String, message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.t(tag).w(message, *args)
    }

    fun e(tag: String, throwable: Throwable, message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.t(tag).e(throwable, message, *args)
    }

    fun e(tag: String, message: String, vararg args: Any) {
        if (BuildConfig.DEBUG) Logger.t(tag).e(message, *args)
    }

    // ====================== 兼容原生Log的极简方法（可选） ======================
    /**
     * 极简Debug日志（自动取调用类名作为Tag）
     */
    fun simpleD(message: String) {
        if (BuildConfig.DEBUG) {
            val tag = getCallerClassName()
            Log.d(tag, message)
        }
    }

    /**
     * 极简Error日志（自动取调用类名作为Tag，带异常）
     */
    fun simpleE(throwable: Throwable, message: String) {
        if (BuildConfig.DEBUG) {
            val tag = getCallerClassName()
            Log.e(tag, message, throwable)
        }
    }

    /**
     * 私有方法：获取调用者的类名（用于simple系列日志的自动Tag）
     */
    private fun getCallerClassName(): String {
        return try {
            // 获取调用栈，定位到LogUtils外部的调用类
            val stackTrace = Thread.currentThread().stackTrace
            val callerStackIndex = 4 // 栈索引：0=VM,1=Thread,2=getCallerClassName,3=simpleD/E,4=外部调用者
            val callerClass = Class.forName(stackTrace&#91;callerStackIndex].className)
            callerClass.simpleName // 只取类名（不含包名），简化Tag
        } catch (e: Exception) {
            DEFAULT_TAG // 异常时使用默认Tag
        }
    }

    /**
     * 清空日志（Release模式下无操作）
     */
    fun clear() {
        if (BuildConfig.DEBUG) Logger.clearLogAdapters()
    }
}</code></pre>



<p class="wp-block-paragraph">在使用之前，需要在<code>Application</code> 中调用：</p>



<pre class="wp-block-code"><code>LogUtils.init()
//记得在Manifest文件中检查是否为自定义的Application</code></pre>



<p class="wp-block-paragraph">使用示例</p>



<pre class="wp-block-code"><code>import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import your.package.app.common.LogUtils // 导入你的日志工具类

class MainActivity : AppCompatActivity() {
    private val USER_ID = 1001

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 使用封装后的日志方法，它会在 Debug 包中格式化输出
        LogUtils.d("MainActivity 已启动") // 无Tag，使用默认Tag
        
        // 使用带参数的格式化日志
        LogUtils.i("用户 %d 正在加载数据...", USER_ID) 
        
        // 使用自定义 Tag
        LogUtils.w("UI", "Warning: 布局文件加载可能耗时") 
        
        // 模拟一个异常日志
        try {
            // 模拟抛出异常
            throw RuntimeException("网络连接失败")
        } catch (e: Exception) {
            // 输出带异常堆栈的日志
            LogUtils.e(e, "数据加载失败，请检查网络！") 
        }
    }
}</code></pre>



<p class="wp-block-paragraph">最终可能的输出效果：</p>



<pre class="wp-block-code"><code>
// LogUtils.d("MainActivity 已启动") 的输出
D/MY_APP: ┌────────────────────────────────────────────────────────
D/MY_APP: │ MainActivity.onCreate (MainActivity.kt:15)
D/MY_APP: ├────────────────────────────────────────────────────────
D/MY_APP: │ MainActivity 已启动
D/MY_APP: └────────────────────────────────────────────────────────

// LogUtils.e(e, "数据加载失败，请检查网络！") 的输出
E/MY_APP: ┌────────────────────────────────────────────────────────
E/MY_APP: │ MainActivity.onCreate (MainActivity.kt:28)
E/MY_APP: ├────────────────────────────────────────────────────────
E/MY_APP: │ RuntimeException: 网络连接失败
E/MY_APP: │   at your.package.app.MainActivity.onCreate(MainActivity.kt:24)
E/MY_APP: │   ... (省略部分堆栈)
E/MY_APP: ├────────────────────────────────────────────────────────
E/MY_APP: │ 数据加载失败，请检查网络！
E/MY_APP: └────────────────────────────────────────────────────────</code></pre>



<p class="wp-block-paragraph">图片来自： <a href="https://www.pexels.com/zh-cn/photo/1260324">https://www.pexels.com/zh-cn/photo/1260324</a></p>
<p><a href="https://www.log.show/log/android-log-utils-wrapper/">一个Android的Log类</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.log.show/log/android-log-utils-wrapper/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>解决Glide加载Loop count（循环次数）为1的gif依然多次播放的问题</title>
		<link>https://www.log.show/log/glide-load-image-loop-count-one-time/</link>
					<comments>https://www.log.show/log/glide-load-image-loop-count-one-time/#comments</comments>
		
		<dc:creator><![CDATA[LOGGER]]></dc:creator>
		<pubDate>Wed, 19 Nov 2025 05:36:43 +0000</pubDate>
				<category><![CDATA[LOG]]></category>
		<category><![CDATA[Android开发]]></category>
		<category><![CDATA[GifDrawable]]></category>
		<category><![CDATA[GIF播放]]></category>
		<category><![CDATA[Glide]]></category>
		<category><![CDATA[LOOP_INTRINSIC]]></category>
		<category><![CDATA[元数据]]></category>
		<category><![CDATA[动画控制]]></category>
		<category><![CDATA[图片加载]]></category>
		<category><![CDATA[循环次数]]></category>
		<category><![CDATA[聊天界面]]></category>
		<guid isPermaLink="false">https://print.surf/?p=258</guid>

					<description><![CDATA[<p>问题出现 在使用Glide加载一个聊天页面的表情包。之前的表情包都是循环播放，没有任何问题。现在加入了猜拳和骰 [&#8230;]</p>
<p><a href="https://www.log.show/log/glide-load-image-loop-count-one-time/">解决Glide加载Loop count（循环次数）为1的gif依然多次播放的问题</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">问题出现</h2>



<p class="wp-block-paragraph">在使用Glide加载一个聊天页面的表情包。之前的表情包都是循环播放，没有任何问题。现在加入了猜拳和骰子，这类表情包需要播放到1次末尾的时候停止播放，进而展示当前随机到的内容是什么。</p>



<p class="wp-block-paragraph">现在使用的方案是使用只会播放1次的gif进行加载。下面是我的加载代码：</p>



<pre class="wp-block-code"><code>            GlideApp.with(tv.context)
                .asGif()
                .load(msgDto.msg)
                .override(dp2px(tv.context, 25), dp2px(tv.context, 25))
                .into(imageView)</code></pre>



<p class="wp-block-paragraph">那么现在的问题是什么呢？</p>



<ul class="wp-block-list">
<li>加载Loop count = 0的gif，循环播放</li>



<li>加载loop count = 1的gif，会出现有的播放一次，有的循环播放</li>
</ul>



<h2 class="wp-block-heading">解决问题</h2>



<h3 class="wp-block-heading">尝试方案</h3>



<p class="wp-block-paragraph">我第一时间猜测是GIF图片的播放次数问题。因此我使用了ezgif.com ，Photoshop，在线PS等各种工具进行文件导出，尝试重新设置图片循环次数。</p>



<p class="wp-block-paragraph">结果图片循环次数是加上了，但是Android不可以单次播放。（ios正常加载）</p>



<p class="wp-block-paragraph">通常的排除法此时失效：</p>



<ol class="wp-block-list">
<li>iOS可用，Android不可以，可以排除图片的问题❓</li>



<li>其他播放一次的gif可以适配，这个只播放一次的gif适配不了，可以排除加载方式的问题❓</li>
</ol>



<p class="wp-block-paragraph">问题不难，真的折磨人。下面是解决思路：</p>



<h3 class="wp-block-heading">问题原因</h3>



<p class="wp-block-paragraph">根本原因在于不同的GIF文件在制作时，对于“Loop Count（循环次数）”的元数据（Metadata）写入标准不一致，或者部分文件缺少该头部信息。</p>



<p class="wp-block-paragraph">当Glide加载这些文件时，如果没有显式指定行为，<code>GifDrawable</code> 可能会根据缺省设置或者被复用的View状态，导致某些本该只播放一次的图变成了无限循环。</p>



<p class="wp-block-paragraph">为了通过<strong>同一个语句</strong>完美支持“无限循环”和“只播放一次”的GIF，你需要显式地告诉 Glide：<strong>“请严格按照GIF文件原本定义的次数播放（Intrinsic）”。</strong></p>



<p class="wp-block-paragraph">下面可用的加载代码：</p>



<pre class="wp-block-code"><code>import com.bumptech.glide.load.resource.gif.GifDrawable
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException

Glide.with(tv.context)
    .asGif()
    .load(msgDto.msg)
    .override(dp2px(tv.context, 25), dp2px(tv.context, 25))
    .listener(object : RequestListener&lt;GifDrawable> {
        override fun onLoadFailed(
            e: GlideException?, 
            model: Any?, 
            target: Target&lt;GifDrawable>?, 
            isFirstResource: Boolean
        ): Boolean {
            return false
        }

        override fun onResourceReady(
            resource: GifDrawable?, 
            model: Any?, 
            target: Target&lt;GifDrawable>?, 
            dataSource: DataSource?, 
            isFirstResource: Boolean
        ): Boolean {
            resource?.setLoopCount(GifDrawable.LOOP_INTRINSIC)
            return false
        }
    })
    .into(imageView)</code></pre>



<p class="wp-block-paragraph">基本可以解决很多问题。</p>
<p><a href="https://www.log.show/log/glide-load-image-loop-count-one-time/">解决Glide加载Loop count（循环次数）为1的gif依然多次播放的问题</a>最先出现在<a href="https://www.log.show">日志</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.log.show/log/glide-load-image-loop-count-one-time/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
