Android Handler.postDelayed 挖坑记

平台

  RK3288 + Android 7.1 + AndroidStudio 4.0.1

问题

    使用Handler

Handler.postDelayed(Runnable, Int)

    时, Runnable.run中的代码不执行

分析

  Handler 部分的代码:

    static class H extends Handler{
        final static int MSG_REFRESH_TIME = 0;
        WeakReference<KidsLauncher> a;
        H(KidsLauncher lk){
            a = new WeakReference<>(lk);
        }

        @Override
        public void handleMessage(Message msg) {
            switch(msg.what){
                case MSG_REFRESH_TIME:
                    a.get().refreshTime();
                    break;
            }
        }

        /*@Override
        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            Logger.d("sendmessageAtTime " + msg.what + " " + uptimeMillis + ":" + SystemClock.uptimeMillis());
            return super.sendMessageAtTime(msg, uptimeMillis);
        }*/
    }


    void refreshTime(){
        tvTime.setText(TimeUtils.getTimeString());
        h.removeMessages(H.MSG_REFRESH_TIME);
        h.sendEmptyMessageDelayed(H.MSG_REFRESH_TIME, 1000);
    }

尝试过的解决方案:

  1. 使用new Handler().postDelayed                      => 问题依旧
  2. 使用new Handler().postDelayed, delayed = 0 => 问题解决, 但无法延时
  3. 使用Handler.post()                                           => 问题解决, 但无法延时
  4. 使用Handler.sendEmptyMessageDelayed      => 问题解决
  5. 使用Handler.sendMessageDelayed                => 问题解决
  6. 使用View.postDelayed                                     => 问题依旧

查阅相关源码及文章:

 

Handler sendMessageDelayed()/postDelayed()机制详解

 

尝试把上面注释代码放开, 输出LOG, 终于发现了问题所在.

解决

原因: 在应用中, 定义一个消息类型为 MSG_REFRESH_TIME=0; 在启动后, 会自循环更新当前的时间.

考虑下这样一个问题: Handler.post后, 最终enqueueMessage时, Message.what是多少?  答案是 0

//frameworks/base/core/java/android/os/Handler.java

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }



    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

不管是new Message(), 还是Message.obtain(), 在初始时, 基本都是0

再看下面这段代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        h = new H(this);
        h.postDelayed(new Runnable() {
            @Override
            public void run() {
                Logger.d("run in post Delayed");
            }
        }, 2000);
        h.removeMessages(0);
    }

代码中 "run in post Delayed" 这个LOG, 是不会打印出来的, 原因是, Message已经从队列移除了.

同理, 在使用MSG_REFRESH_TIME 更新时间的时, 执行refreshTime后, 队列中, 所有的带Runnable的Message已经被h.removeMessages(H.MSG_REFRESH_TIME); 移出了队列.

所以这个问题是, 自己挖坑, 再把自己埋了...

 

 

<think>我们正在处理一个关于在Android Studio中使用Handler.postDelayed方法时ImageView崩溃的问题。根据提供的引用和用户的问题,我们需要分析可能的原因并给出解决方案。 引用[1]提到postDelayed创建一个HandlerAction数组保存任务。引用[2]和[3]解释了Handler的post方法最终会通过sendMessage将任务放到消息队列中,并在创建Handler的线程中执行。因此,我们在使用Handler.postDelayed时,需要确保对ImageView的操作是在UI线程中执行的,因为Android要求UI操作必须在主线程(UI线程)中执行。 但是,用户提到在Handler.postDelayed方法里操作ImageView时崩溃。通常,这种情况可能是由于以下原因: 1. 在非UI线程中更新ImageView(尽管Handler通常是在创建它的线程中执行,但如果Handler是在非主线程创建的,那么它就会在那个线程执行,导致问题)。 2. 在ImageView已经被销毁(例如Activity已经结束)后,仍然尝试更新它,导致空指针异常。 3. 在更新ImageView时传递了无效的资源或参数。 根据引用[4]和[5],用户可能还尝试了动画效果(如图片翻转、旋转等),但问题出现在Handler.postDelayed中,所以我们需要关注Handler的使用。 解决方案: 1. 确保Handler是在主线程(UI线程)中创建的。在Activity中,通常我们直接声明Handler,因为Activity运行在主线程,所以它的Handler也会在主线程处理消息。 例如:`Handler handler = new Handler(Looper.getMainLooper());` 或者直接`new Handler()`(在UI线程中创建)。 2.Runnable中更新ImageView之前,先检查Activity是否还存在(避免在Activity销毁后更新UI导致崩溃)。可以通过弱引用或者判断Activity状态来实现。 3. 如果使用了动画,确保动画的操作是合法的(例如,旋转动画的pivotX和pivotY设置正确,图片资源存在等)。 具体步骤: 步骤1:创建Handler时确保关联主线程的Looper 如果Handler是在非主线程创建的,那么需要指定主线程的Looper。在Activity中,通常可以直接创建,因为Activity运行在主线程。但如果在子线程中创建Handler,则需要: Handler mHandler = new Handler(Looper.getMainLooper()); 步骤2:在Runnable中更新ImageView时检查视图的有效性 在Runnable中,首先检查ImageView是否已经被销毁(例如Activity被销毁),可以使用isFinishing()(对于Activity)或isDestroyed()(对于Activity)来判断,但是注意,在Runnable能直接访问Activity的这些方法,因此可以: a. 使用弱引用持有Activity或ImageView b.Runnable执行之前,先判断Activity的状态 示例代码: // 在Activity中 private Handler mHandler = new Handler(Looper.getMainLooper()); private Runnable mRunnable = new Runnable() { @Override public void run() { // 检查Activity是否已经结束 if (isFinishing() || isDestroyed()) { return; } // 更新ImageView imageView.setImageResource(R.drawable.new_image); // 或者执行动画等操作 } }; 然后使用:mHandler.postDelayed(mRunnable, 1000); 步骤3:在Activity销毁时移除回调 为了避免Activity销毁后Handler仍然执行,需要在onDestroy中移除所有回调: @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacks(mRunnable); // 移除指定的Runnable // 或者 mHandler.removeCallbacksAndMessages(null); // 移除所有 } 步骤4:检查资源是否存在 确保在setImageResource等方法中使用的资源ID是有效的。 步骤5:如果使用动画,确保动画操作的正确性 例如,使用旋转动画(如引用[5]的RotateDrawable)时,确保在代码中正确设置旋转角度和旋转中心。 如果用户的问题是由于在非UI线程更新UI引起的,那么上述步骤1和2可以解决。如果是因为在Activity销毁后更新UI,那么步骤2和3可以解决。 另外,用户提到的崩溃是什么错误?常见的错误有: - NullPointerException: 可能ImageView为null(例如findViewById失败,或者已经销毁) - CalledFromWrongThreadException: 在非UI线程更新UI - 其他异常(如资源未找到等) 因此,我们需要用户提供崩溃日志,但根据问题描述,我们给出通用的解决方案。 总结解决办法: 1. 确保Handler关联主线程。 2.Runnable中检查Activity状态。 3. 在Activity销毁时移除回调。 4. 确保资源有效。 示例代码: public class MainActivity extends AppCompatActivity { private Handler mHandler; private ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImageView = findViewById(R.id.imageView); // 创建Handler,关联主线程Looper mHandler = new Handler(Looper.getMainLooper()); // 延迟1秒执行 mHandler.postDelayed(new Runnable() { @Override public void run() { // 检查Activity是否已经销毁 if (isFinishing() || isDestroyed()) { return; } // 更新ImageView mImageView.setImageResource(R.drawable.ic_launcher_background); } }, 1000); } @Override protected void onDestroy() { super.onDestroy(); // 移除所有回调 mHandler.removeCallbacksAndMessages(null); } } 如果用户是在Fragment中,注意使用getActivity()检查Activity是否为空或已销毁。 如果用户的问题仍然存在,建议检查Logcat中的具体错误信息。 相关问题: 1. 如何在Android中正确使用Handler? 2. 如何避免内存泄漏:在Activity中使用Handler时的注意事项? 3. 如何实现ImageView的旋转动画? 4. 如何在Activity销毁时取消Handler的延时任务? 注意:引用[4]和[5]提供了关于动画的信息,如果用户的问题与动画有关,也可以参考这些引用。 </think>### Android StudioHandler.postDelayed方法里ImageView崩溃的解决方案 #### 问题分析 在Handler.postDelayed方法中操作ImageView时出现崩溃,通常由以下原因导致: 1. **线程问题**:在非UI线程更新ImageView(违反Android的视图更新规则)[^2][^3] 2. **生命周期问题**:Activity/Fragment销毁后仍尝试更新ImageView(空指针异常) 3. **资源状态问题**:操作已被销毁的ImageView或无效资源 4. **动画冲突**:旋转/平移动画与Handler任务冲突(尤其引用[4][^4][^5]提到的动画场景) --- #### 解决方案 **1. 确保在主线程更新UI(核心)** ```java // 创建关联主线程Looper的Handler Handler mainHandler = new Handler(Looper.getMainLooper()); mainHandler.postDelayed(() -> { // 检查视图有效性 if (isActivityValid() && imageView != null) { // 安全操作ImageView(示例:翻转动画) imageView.setRotation(180f); } }, 1000); // 延迟1秒 // 检查Activity状态方法 private boolean isActivityValid() { return !isFinishing() && !isDestroyed(); } ``` **2. 生命周期管理(必做)** ```java @Override protected void onDestroy() { super.onDestroy(); // 移除所有Handler回调 mainHandler.removeCallbacksAndMessages(null); } ``` **3. 视图空指针防护** ```java // 使用弱引用避免内存泄漏 private WeakReference<ImageView> weakImageView = new WeakReference<>(imageView); mainHandler.postDelayed(() -> { ImageView target = weakImageView.get(); if (target != null && target.getContext() != null) { target.setImageResource(R.drawable.safe_image); } }, 500); ``` **4. 动画资源冲突解决(引用[4][^4]场景)** ```xml <!-- 引用[5]的旋转动画兼容方案 --> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/a1" android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0" android:toDegrees="180"/> <!-- 避免360°循环导致冲突 --> ``` ```java // 代码中停止动画再更新 if (imageView.getAnimation() != null) { imageView.clearAnimation(); } imageView.startAnimation(yourNewAnimation); ``` --- #### 关键注意事项 1. **Handler创建位置**: - ✅ 正确:`new Handler(Looper.getMainLooper())` - ❌ 错误:在子线程直接`new Handler()` 2. **性能优化**: ```java // 复用Runnable对象避免内存分配 private final Runnable updateTask = () -> { if (imageView != null) imageView.performAction(); }; mainHandler.postDelayed(updateTask, 1000); ``` 3. **Kotlin扩展方案**: ```kotlin // 添加生命周期感知的扩展函数 fun ImageView.safePostDelayed(delay: Long, action: () -> Unit) { val context = context as? Activity ?: return Handler(Looper.getMainLooper()).postDelayed({ if (!context.isDestroyed) action() }, delay) } // 使用示例 imageView.safePostDelayed(2000) { rotation = 90f } ``` --- #### 典型崩溃场景对照表 | 崩溃日志特征 | 解决方法 | |------------------------------|------------------------------| | `CalledFromWrongThreadException` | 使用`Looper.getMainLooper()` | | `NullPointerException` | 弱引用+生命周期检查 | | `IllegalArgumentException` | 清除动画状态[^4][^5] | | `Surface already released` | 延迟时间超过生命周期 | > 提示:始终在`onPause()`/`onDestroy()`中移除回调,可避免90%的崩溃[^1][^2][^3]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值