ChatGPT解决这个技术问题 Extra ChatGPT

如何在Android延迟后调用方法

我希望能够在指定的延迟后调用以下方法。在目标 c 中有类似的东西:

[self performSelector:@selector(DoSomething) withObject:nil afterDelay:5];

在带有java的android中是否有这种方法的等价物?例如,我需要能够在 5 秒后调用一个方法。

public void DoSomething()
{
     //do something here
}

R
Rahul Gaur

科特林

Handler(Looper.getMainLooper()).postDelayed({
    //Do something after 100ms
}, 100)

爪哇

final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something after 100ms
    }
}, 100);


此解决方案仅在 UI 线程上有用。否则在普通线程上,你需要实现 Looper,这不是我认为的最佳版本
@olivier_sdg 为什么需要实现 looper?
@djechlin 处理程序必须始终链接到 Looper,它将实际处理您发布的 Runnable()。 UI 线程已经带有 Looper,因此您可以在 UI 线程上创建一个新的 Handler() 并将 post() Runnables 直接发送到它。这些 Runnable 在 UI 线程上执行。要让 Runnables 在另一个线程上执行,您需要创建一个新线程,然后是 Looper.prepare(),创建一个新的 Handler(),然后是 Looper.loop()。任何发布到这个新 Handler 的 Runnables 都将在这个新线程上执行。如果您不执行所有这些操作,post() 将抛出异常。
如果需要,您还可以通过在 Handler 上调用 removeCallbacks(Runnable r)取消执行,只要 Runnable 仍在消息队列中。
应该import android.os.handler
J
Jules Colle

在我的情况下,我无法使用任何其他答案。我改用本机 java Timer。

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // this code will be executed after 2 seconds       
    }
}, 2000);

这比使用 Handler 的要好,因为当 Handler 不在 UI 线程上运行时,它没有 Looper 问题。
您应该保留对计时器的引用,以便在不再需要它时取消它,因为根据 Android 文档:“当不再需要计时器时,用户应该调用 cancel(),它会释放计时器的线程和其他资源。未明确取消的计时器可能会无限期地持有资源。”
注意力!这不在 UI 线程上运行。在 ui 线程上运行它会导致致命错误:android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。
@vovahost 这只是因为您正在更新计时器块内的 UI 组件
请注意,java.util.Timer(和 TimerTask)将在 JDK 9 中被弃用。 TimerTask 为不太好的任务创建新线程。
C
Community

注意:当问题没有将 Android 指定为上下文时给出了这个答案。对于特定于 Android UI 线程的答案 look here.

看起来 Mac OS API 让当前线程继续运行,并安排任务异步运行。在 Java 中,等效功能由 java.util.concurrent 包提供。我不确定 Android 可能会施加什么限制。

private static final ScheduledExecutorService worker = 
  Executors.newSingleThreadScheduledExecutor();

void someMethod() {
  ⋮
  Runnable task = new Runnable() {
    public void run() {
      /* Do something… */
    }
  };
  worker.schedule(task, 5, TimeUnit.SECONDS);
  ⋮
}

这永远不会为我调用 Runnable
附带说明:这还允许您稍后取消任务,这在某些情况下可能会有所帮助。只需存储对 worker.schedule() 返回的 ScheduledFuture<?> 的引用并调用其 cancel(boolean) 方法。
我认为这个答案已经过时了。 .schedule 似乎不再是 Runnable 的方法......? :/
@beetree 这是 ScheduledExecutorService 上的一个方法。
如果涉及 ui 线程对象,这不起作用,您必须调用 runOnUIThread(new runnable(){ run()....});或使用 run(){ } 内部的处理程序对象发布可运行对象
J
Jared Rummler

用于在 5 秒后在 UI 线程中执行某些操作:

new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something here
    }
}, 5000);

确认,这是防止调用 looper.prepare 并将整个事情放入 UI 线程的最佳解决方案。
谢谢你,帮我解决了 Looper 问题:)
我会小心在主循环器上创建一个处理程序,然后在这个线程中不应该完成长时间的任务
K
Khemraj Sharma

Kotlin 和 Java 多种方式

1.使用处理程序

Handler().postDelayed({
    TODO("Do something")
    }, 2000)

2. 使用 TimerTask

Timer().schedule(object : TimerTask() {
    override fun run() {
        TODO("Do something")
    }
}, 2000)

甚至更短

Timer().schedule(timerTask {
    TODO("Do something")
}, 2000)

或者最短的是

Timer().schedule(2000) {
    TODO("Do something")
}

3. 使用执行器

Executors.newSingleThreadScheduledExecutor().schedule({
    TODO("Do something")
}, 2, TimeUnit.SECONDS)

在 Java 中

1.使用处理程序

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something
    }
}, 2000);

2. 使用定时器

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // Do something
    }
}, 2000);

3.使用ScheduledExecutorService

private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();

Runnable runnable = new Runnable() {
  public void run() {
      // Do something
  }
  };
worker.schedule(runnable, 2, TimeUnit.SECONDS);

@JanRabe 感谢您的建议。我很欣赏它。但是问题是How to call a method after a delay in Android。所以我专注于此。说到点子上了。否则,Java 泄漏对于开发人员来说是一个需要单独理解的大话题。
Handler().postDelayed({ }, 2000) 显示为已删除
E
Etienne Kaiser

你可以在 UIThread 中使用 Handler:

runOnUiThread(new Runnable() {
                
    @Override
    public void run() {
        new Handler().postDelayed(new Runnable() {
           @Override
           public void run() {
               //add your code here
          }
        }, 1000);           
    }
});

J
Jared Rummler

感谢所有出色的答案,我找到了最适合我需求的解决方案。

Handler myHandler = new DoSomething();
Message m = new Message();
m.obj = c;//passing a parameter here
myHandler.sendMessageDelayed(m, 1000);

class DoSomething extends Handler {
    @Override
    public void handleMessage(Message msg) {
      MyObject o = (MyObject) msg.obj;
      //do something here
    }
}

如果我使用这种方法在单击项目时获得触摸反馈是否可以.. view.setColor(some_color) 然后在 x 秒后在 Handler 中删除此颜色...?
O
OscarRyz

看这个演示:

import java.util.Timer;
import java.util.TimerTask;

class Test {
     public static void main( String [] args ) {
          int delay = 5000;// in ms 

          Timer timer = new Timer();

          timer.schedule( new TimerTask(){
             public void run() { 
                 System.out.println("Wait, what..:");
              }
           }, delay);

           System.out.println("Would it run?");
     }
}

n
noob

如果您必须使用 Handler,但您进入了另一个线程,您可以使用 runonuithread 在 UI 线程中运行该处理程序。这将使您免于抛出要求调用 Looper.Prepare() 的异常

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //Do something after 1 second
            }
        }, 1000);
    }
});

看起来很乱,但这是其中一种方式。


这行得通,我无法编辑您的帖子,因为愚蠢的 SO 规则至少要编辑 6 个字符,但是在“新处理程序”之后缺少“()”它应该是“新处理程序()”
您可以执行以下操作,而不是将所有内容都放入 UI 线程: new Handler(Looper.getMainLooper())
A
Alexander Farber

我更喜欢使用 View.postDelayed() 方法,下面的简单代码:

mView.postDelayed(new Runnable() {
    @Override
    public void run() {
        // Do something after 1000 ms
    }
}, 1000);

它不会冻结 ui 元素本身,因为它将被安排在视图处理程序上吗?
不,发布的任务将在 1 秒内执行,但在这第二个 UI 线程执行其他有用的工作
W
Wilson Tran

更安全 - 使用 Kotlin 协程

大多数答案都使用 Handler 但我给出了一个不同的解决方案来延迟活动、片段、视图模型与 Android Lifecycle ext。这种方式将在生命周期开始被破坏时自动取消 - 避免泄漏内存或崩溃的应用程序

在活动或片段中:

lifecycleScope.launch { 
  delay(DELAY_MS)
  doSomething()
}

在视图模型中:

viewModelScope.lanch {
  delay(DELAY_MS)
  doSomething()
}

在挂起函数中:(Kotlin Coroutine)

suspend fun doSomethingAfter(){
    delay(DELAY_MS)
    doSomething()
}

如果您收到未找到生命周期范围的错误! - 将此依赖项导入应用程序 gradle 文件:

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"

我认为协程方法更好。尤其是当您的范围与组件 Activity、Fragment、具有生命周期的自定义组件绑定时。大多数时候,要求是在主机还活着的时候执行一个方法。我还建议让 Job 实例支持逻辑取消。例如: val job = scope.launch {....} .... .... // 在延迟作业开始执行之前取消它 job.cancel()
T
Tunaki

这是我最短的解决方案:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something after 100ms
    }
}, 100);

v
vovahost

如果您使用的是 Android Studio 3.0 及更高版本,则可以使用 lambda 表达式。 2 秒后调用方法 callMyMethod()

new Handler().postDelayed(() -> callMyMethod(), 2000);

如果您需要取消延迟的可运行,请使用以下命令:

Handler handler = new Handler();
handler.postDelayed(() -> callMyMethod(), 2000);

// When you need to cancel all your posted runnables just use:
handler.removeCallbacksAndMessages(null);

我们怎样才能取消这个?
我很惊讶这里有多少人会很高兴地转向 Kotlin,却完全忽略了标准 Java 的 Lambda 表达式。
J
Jared Rummler
final Handler handler = new Handler(); 
Timer t = new Timer(); 
t.schedule(new TimerTask() { 
    public void run() { 
        handler.post(new Runnable() { 
            public void run() { 
                //DO SOME ACTIONS HERE , THIS ACTIONS WILL WILL EXECUTE AFTER 5 SECONDS...
            }
        }); 
    } 
}, 5000); 

N
Nate

我建议使用 Timer,它允许您安排在非常特定的时间间隔调用方法。这不会阻塞您的 UI,并在执行该方法时使您的应用程序保持响应。

另一个选项是 wait(); 方法,这将阻塞当前线程指定的时间长度。如果您在 UI 线程上执行此操作,这将导致您的 UI 停止响应。


Thread.sleep() 比 Object.wait() 好。等待意味着您希望收到通知并围绕某些活动进行同步。睡眠表示您只想在指定的时间内什么都不做。如果您希望操作在稍后的某个时间点异步发生,则计时器是一种方法。
那是真实的。这就是为什么我将它列为另一个选项;-)
S
Sam

所以这里有一些事情需要考虑,因为有很多方法可以给这只猫剥皮。虽然答案都已经给出了选择和选择。我认为重要的是通过适当的编码指南重新审视这一点,以避免任何人仅仅因为“多数选择简单答案”而走错方向。

因此,首先让我们讨论简单的 Post Delayed 答案,它是该线程中的获胜者选择的答案。

有几件事需要考虑。在 post 延迟之后,您可能会遇到内存泄漏、死对象、已经消失的生命周期等等。所以正确处理也很重要。你可以通过几种方式做到这一点。

为了现代发展,我将在 KOTLIN 中提供

这是一个在回调上使用 UI 线程的简单示例,并在您点击回调时确认您的 Activity 仍然存在且运行良好。

  Handler(Looper.getMainLooper()).postDelayed({
            if(activity != null && activity?.isFinishing == false){
                txtNewInfo.visibility = View.GONE
            }
        }, NEW_INFO_SHOW_TIMEOUT_MS)

但是,这仍然不完美,因为如果活动消失,没有理由点击您的回调。所以更好的方法是保留对它的引用并像这样删除它的回调。

    private fun showFacebookStylePlus1NewsFeedOnPushReceived(){
        A35Log.v(TAG, "showFacebookStylePlus1NewsFeedOnPushReceived")
        if(activity != null && activity?.isFinishing == false){
            txtNewInfo.visibility = View.VISIBLE
            mHandler.postDelayed({
                if(activity != null && activity?.isFinishing == false){
                    txtNewInfo.visibility = View.GONE
                }
            }, NEW_INFO_SHOW_TIMEOUT_MS)
        }
    }

当然,在 onPause 上处理清理,这样它就不会触发回调。

    override fun onPause() {
        super.onPause()
        mHandler.removeCallbacks(null)
    }

现在我们已经讨论了显而易见的问题,让我们来谈谈现代协程和 kotlin 的更清洁选项:)。如果你还没有使用这些,你真的错过了。

   fun doActionAfterDelay() 
        launch(UI) {
            delay(MS_TO_DELAY)           
            actionToTake()
        }
    }

或者,如果您想始终使用该方法启动 UI,您可以简单地执行以下操作:

  fun doActionAfterDelay() = launch(UI){ 
      delay(MS_TO_DELAY)           
      actionToTake()
  }

当然,就像 PostDelayed 一样,您必须确保处理取消,以便您可以在延迟调用之后进行活动检查,或者您可以像其他路线一样在 onPause 中取消它。

var mDelayedJob: Job? = null
fun doActionAfterDelay() 
   mDelayedJob = launch(UI) {
            try {
               delay(MS_TO_DELAY)           
               actionToTake()
            }catch(ex: JobCancellationException){
                showFancyToast("Delayed Job canceled", true, FancyToast.ERROR, "Delayed Job canceled: ${ex.message}")
            }
        }
   }
}

//处理清理

override fun onPause() {
   super.onPause()
   if(mDelayedJob != null && mDelayedJob!!.isActive) {
      A35Log.v(mClassTag, "canceling delayed job")
      mDelayedJob?.cancel() //this should throw CancelationException in coroutine, you can catch and handle appropriately
   }
}

如果您将启动(UI)放入方法签名中,则可以在调用代码行中分配作业。

所以这个故事的寓意是确保您的延迟操作是安全的,确保您删除您的回调,或取消您的工作,当然要确认您有正确的生命周期来触摸延迟回调完成的项目。协程还提供可取消的操作。

另外值得注意的是,您通常应该处理协程可能带来的各种异常。例如,取消、异常、超时,无论您决定使用什么。如果您决定真正开始使用协程,这里有一个更高级的示例。

   mLoadJob = launch(UI){
            try {
                //Applies timeout
                withTimeout(4000) {
                    //Moves to background thread
                    withContext(DefaultDispatcher) {
                        mDeviceModelList.addArrayList(SSDBHelper.getAllDevices())
                    }
                }

                //Continues after async with context above
                showFancyToast("Loading complete", true, FancyToast.SUCCESS)
            }catch(ex: JobCancellationException){
                showFancyToast("Save canceled", true, FancyToast.ERROR, "Save canceled: ${ex.message}")
            }catch (ex: TimeoutCancellationException) {
                showFancyToast("Timed out saving, please try again or press back", true, FancyToast.ERROR, "Timed out saving to database: ${ex.message}")
            }catch(ex: Exception){
                showFancyToast("Error saving to database, please try again or press back", true, FancyToast.ERROR, "Error saving to database: ${ex.message}")
            }
        }

没问题 Rajiv,我会更进一步,并提到使用 Live Data 协程可以感知生命周期并自我取消以避免清理调用,但不想在一个答案中投入太多学习曲线;)
M
Mr T

对于简单的线处理后延迟,您可以执行以下操作:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        // Do someting
    }
}, 3000);

我希望这有帮助


C
Crime_Master_GoGo

您可以将其用于最简单的解决方案:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Write your code here
    }
}, 5000); //Timer is in ms here.

否则,下面可能是另一个干净有用的解决方案:

new Handler().postDelayed(() -> 
{/*Do something here*/}, 
5000); //time in ms

P
Pang

您可以使用新引入的 lambda 表达式使其更简洁:

new Handler().postDelayed(() -> {/*your code here*/}, time);

S
Samuel Liew

使用 Kotlin,我们可以通过执行以下操作来实现

Handler().postDelayed({
    // do something after 1000ms 
}, 1000)

R
Rahul Gaur

如果您使用 RxAndroid,那么线程和错误处理会变得更加容易。以下代码在延迟后执行

Observable.timer(delay, TimeUnit.SECONDS)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(aLong -> {
            // Execute code here
        }, Throwable::printStackTrace);

H
HelmiB

我创建了更简单的方法来调用它。

public static void CallWithDelay(long miliseconds, final Activity activity, final String methodName)
    {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                try {
                    Method method =  activity.getClass().getMethod(methodName);
                    method.invoke(activity);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }, miliseconds);
    }

要使用它,只需调用:.CallWithDelay(5000, this, "DoSomething");


对于这样一个基本任务的反思?
由于问题调用方法类似于 iOS performSelector。这是最好的方法。
S
Sazzad Hissain Khan

当你得到以下一个作品时,

java.lang.RuntimeException:无法在未调用 Looper.prepare() 的线程内创建处理程序

final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);

t
tronman

使用 CountDownTimer 非常简单。更多详情https://developer.android.com/reference/android/os/CountDownTimer.html

import android.os.CountDownTimer;

// calls onTick every second, finishes after 3 seconds
new CountDownTimer(3000, 1000) { 

   public void onTick(long millisUntilFinished) {
      Log.d("log", millisUntilFinished / 1000);
   }

   public void onFinish() {
      // called after count down is finished
   } 
}.start();

T
Thiago

我喜欢更干净的东西:这是我的实现,在您的方法中使用的内联代码

new Handler().postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);

D
Dan Alboteanu

每个人似乎都忘记在 Handler 上发布新的可运行文件或消息之前对其进行清理。否则,它们可能会累积并导致不良行为。

handler.removeMessages(int what);
// Remove any pending posts of messages with code 'what' that are in the message queue.

handler.removeCallbacks(Runnable r)
// Remove any pending posts of Runnable r that are in the message queue.

P
Pang

这是另一个棘手的方法:当可运行更改 UI 元素时它不会抛出异常。

public class SimpleDelayAnimation extends Animation implements Animation.AnimationListener {

    Runnable callBack;

    public SimpleDelayAnimation(Runnable runnable, int delayTimeMilli) {
        setDuration(delayTimeMilli);
        callBack = runnable;
        setAnimationListener(this);
    }

    @Override
    public void onAnimationStart(Animation animation) {

    }

    @Override
    public void onAnimationEnd(Animation animation) {
        callBack.run();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }
}

你可以这样调用动画:

view.startAnimation(new SimpleDelayAnimation(delayRunnable, 500));

动画可以附加到任何视图。


D
Daniel Wilson

Here is the answer 在 Kotlin 中你们这些懒惰、懒惰的人:

Handler().postDelayed({
//doSomethingHere()
}, 1000)

n
norbDEV

科特林

片段中的 runOnUiThread

定时器

例子:

Timer().schedule(500) {
    activity?.runOnUiThread {
        // code                                    
    }
}

F
Faakhir

android中合适的解决方案:

private static long SLEEP_TIME = 2 // for 2 second
.
.
MyLauncher launcher = new MyLauncher();
            launcher.start();
.
.
private class MyLauncher extends Thread {
        @Override
        /**
         * Sleep for 2 seconds as you can also change SLEEP_TIME 2 to any. 
         */
        public void run() {
            try {
                // Sleeping
                Thread.sleep(SLEEP_TIME * 1000);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            //do something you want to do
           //And your code will be executed after 2 second
        }
    }