如何在Android上在后台线程上运行代码?

170

我希望有一些代码可以持续在后台运行,但不想使用服务。是否还有其他可行的方式?

我已经尝试在我的Activity中调用Thread类,但是我的Activity会在后台停留一段时间然后停止工作,Thread类也会停止工作。

class testThread implements Runnable {
        @Override
        public void run() {
            File file = new File( Environment.getExternalStorageDirectory(), "/BPCLTracker/gpsdata.txt" );
            int i = 0;

            RandomAccessFile in = null;

            try {
                in = new RandomAccessFile( file, "rw" );
            } catch (FileNotFoundException e) {
// TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
// TODO Auto-generated catch block
                e.printStackTrace();
            }
//String line =null;
            while ( true ) {
                HttpEntity entity = null;
                try {
                    if ( isInternetOn() ) {
                        while ( ( line = in.readLine() ) != null ) {

                            HttpClient client = new DefaultHttpClient();
                            String url = "some url";
                            HttpPost request = new HttpPost( url );
                            StringEntity se = new StringEntity( line );
                            se.setContentEncoding( "UTF-8" );
                            se.setContentEncoding( new BasicHeader( HTTP.CONTENT_TYPE, "application/json" ) );
                            entity = se;
                            request.setEntity( entity );
                            HttpResponse response = client.execute( request );
                            entity = response.getEntity();
                            i++;
                        }
                        if ( ( line = in.readLine() ) == null && entity != null ) {
                            file.delete();
                            testThread t = new testThread();
                            Thread t1 = new Thread( t );
                            t1.start();
                        }


                    } else {
                        Thread.sleep( 60000 );
                    } // end of else

                } catch (NullPointerException e1) {
                    e1.printStackTrace();
                } catch (InterruptedException e2) {
                    e2.printStackTrace();
                } catch (IOException e1) {
// TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }// end of while
        }// end of run

    }

1
你好。你能否提供一些代码作为你尝试过的例子吗?有不同的方法可以在后台线程上运行代码,但是你必须记住,如果你正在访问任何UI组件,你必须使用runOnUiThread()方法在UI线程上访问它们。 - Alex Wiese
3
不使用Service是否有特别的原因? - DeltaCap019
这并不是一个推荐的做法。持续运行会耗尽设备电池。 - HelmiB
我已经发送了代码。 - user2082987
我尝试在服务中使用它,但它会多次发送相同的记录。 - user2082987
我知道这会耗电,但我必须这样做。因为我想要连续发送数据。 - user2082987
9个回答

428

如果您需要:

  1. 在后台线程上执行代码

  2. 执行不涉及/更新UI的代码

  3. 执行最多需要几秒钟完成的(短)代码

那么请使用以下干净高效的模式,它使用AsyncTask:

AsyncTask.execute(new Runnable() {
   @Override
   public void run() {
      //TODO your background code
   }
});

10
需要API级别11。 - friederbluemle
16
为什么不使用以下更轻量级的代码:new Thread(new Runnable() { @Override public void run() { // 后台代码 } }).start(); - awardak
3
即使您想要更新UI,您可以在run中使用以下代码片段: activity.runOnUiThread(new Runnable() { @Override public void run() { //更新UI } }); - Deepak S. Gavkar
6
需要注意的一点是AsyncTasks是排队执行的。如果您将其用于一堆服务器API调用,它们将不会并行运行。 - Chase Roberts
显示剩余4条评论

51

记住,运行在后台和连续运行是两个不同的任务。

对于长期后台进程,在Android上使用线程并不是最优的选择。但是,以下是代码供您参考,风险需自负。

正确的方法是先启动服务,然后在服务内部启动需要Runnable的线程/异步任务。

请记住,服务和线程将在后台运行,但我们的任务需要触发器(反复调用)来获取更新,即一旦任务完成,我们需要重新调用函数以获取下一个更新。

定时器(定期触发),闹钟(时间基础触发),广播(事件基础触发),递归会唤醒我们的函数。

public static boolean isRecursionEnable = true;

void runInBackground() {
    if (!isRecursionEnable)
        // Handle not to start multiple parallel threads
        return;

    // isRecursionEnable = false; when u want to stop
    // on exception on thread make it true again  
    new Thread(new Runnable() {
        @Override
        public void run() {
            // DO your work here
            // get the data
            if (activity_is_not_in_background) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // update UI
                        runInBackground();
                    }
                });
            } else {
                runInBackground();
            }
        }
    }).start();
}

使用服务: 如果启动一个服务,它将开始执行任务,并在任务执行完毕后自行终止,除非服务是STICKY的。这种服务终止可能是由于代码中的异常或用户从设置手动杀死它引起的。

START_STICKY(粘性服务)是Android提供的选项,如果服务被终止,该服务将重新启动自身。

记住一个问题多进程和多线程之间的区别? 服务是一种后台进程(就像没有UI的活动一样), 与你在活动中启动线程以避免主线程(活动/UI线程)负载的方式相同,你需要在服务中启动线程以避免负载。

简而言之,如果您想要运行一个后台连续任务,则需要启动StickyService并在服务中基于事件运行线程


你会推荐使用这种方法在后台运行自定义SignalStrengthListener约20分钟,每秒从监听器中获取值以更新UI线程吗?这里有更多的上下文:http://stackoverflow.com/questions/36167719/asynctask-fatal-exception-when-calling-a-method-from-doinbackground?noredirect=1#comment59973788_36167719 - JParks
如果你设置'isRecursionEnable=true',该进程会如何再次运行? 然后,您在“run”方法中调用“runInBackground()”,为什么它不会进入函数开头的if语句? - Mickey

37

简单的三行代码

我在Brandon Rude's answer的评论中发现了一个非常简单的方法,是由@awardak提供的:

new Thread( new Runnable() { @Override public void run() { 
  // Run whatever background code you want here.
} } ).start();

我不确定这是否比使用 AsyncTask.execute 更好,但它似乎对我们有用。 如果有什么区别的评论将不胜感激。

谢谢,@awardak


我们使用 handler.post() 方法在 Thread 的 run 方法内将其发布回主线程。 - androidXP

24
我希望有一些代码能够持续地在后台运行,但我不想用服务实现。是否还有其他可能的方法?
你可能正在寻找的机制是 AsyncTask。它专门用于在后台线程上执行后台进程。此外,它的主要优点是提供了一些在主(UI)线程上运行的方法,并且可以在任务中通知用户有关某些进度或使用从后台进程检索的数据更新 UI。
如果您不知道如何开始,请参考以下教程: 注意:您还可以使用带有ResultReceiverIntentService 实现。

@user2082987,你试过了吗? - Simon Dorociak
这些AsyncTask的参数很令人困惑。 - user2082987
AsyncTask只是Thread的一个包装器,因此它无法解决Android在后台杀死应用程序的问题。 - Lassi Kinnunen
嗨@LassiKinnunen,我写了那个答案5年前。现在一切都改变了,我目前不使用AsyncTask,因为它存在内存泄漏的问题。 - Simon Dorociak
是的,我意识到这一点,但人们仍然会通过谷歌找到这个,并且我已经看到很多人评论推荐使用谷歌包装器,即使它几乎没有任何作用,抱歉。至于最初的问题,如果有人在2018年寻找答案,创建一个服务并启动它,并将其标记为前台通知,以最大化不被杀死的机会。实际的长时间运行过程可以使用处理程序或单独的线程来处理。不过,我没意识到他们实际上做到了不泄漏内存的安全? - Lassi Kinnunen
@SimonDorociak,那么你现在正在使用什么(或将要使用什么)? - mangusta

23

今天我在寻找这个问题的解答,Brandon Rude先生给出了很棒的回答。不幸的是,AsyncTask现在已经被弃用了。你仍然可以使用它,但会收到一个非常烦人的警告。因此,一个替代方案是使用像这样的Executors(在kotlin中):


        Executors.newSingleThreadExecutor().execute(Runnable {
            // todo: do your background tasks
            runOnUiThread{
                // update ui if you are in an activity
            }
            /*
            requireActivity().runOnUiThread {
               // update views / ui if you are in a fragment
            }
            */
        })

而在Java中,它看起来像这样:


        Executors.newSingleThreadExecutor().execute(() -> {
            // todo: background tasks
            runOnUiThread(() -> {
                // todo: update your ui / view in activity
            });

            /*
            requireActivity().runOnUiThread((Runnable) () -> {
                // todo: update your ui / view in Fragment
            });
            */
        });

4

//对我来说工作得很准确

new Thread( () -> {
        //run code on background thread 
        
        activity.runOnUiThread(()->{
            //update the UI on main thread
        });

        //here activity is the reference of activity 
    
            
    }).start();

//OR

 new Thread(new Runnable() {
        @Override
        public void run() {
            //run code on background thread 
        
        activity.runOnUiThread(()->{
            //update the UI on main thread
        });

        //here activity is the reference of activity 
        }
    })

2
class Background implements Runnable {
    private CountDownLatch latch = new CountDownLatch(1);
    private  Handler handler;

    Background() {
        Thread thread = new Thread(this);
        thread.start();
        try {
            latch.await();
        } catch (InterruptedException e) {
           /// e.printStackTrace();
        }
    }

    @Override
    public void run() {
        Looper.prepare();
        handler = new Handler();
        latch.countDown();
        Looper.loop();
    }

    public Handler getHandler() {
        return handler;
    }
}

6
欢迎来到StackOverflow。仅包含代码的答案往往会被标记为"低质量"并被删除。请阅读有关回答问题的帮助部分,然后考虑为您的答案添加一些评论。我将为您进行翻译。 - Graham

2
替代AsyncTask的选择是robospice。https://github.com/octo-online/robospice
一些robospice的特点:
1.异步执行(在后台AndroidService中)网络请求(例如使用Spring Android的REST请求)。当结果准备好时,在UI线程上通知您的应用程序。
2.强类型!您使用POJO进行请求,而且您会得到POJO作为请求结果。
3.不对用于请求的POJO或您在项目中使用的Activity类强制执行任何约束。
4.缓存结果(使用Jackson和Gson的Json格式,或Xml,或平面文本文件,或二进制文件,甚至使用ORM Lite)。
5.仅当它们仍然活着时才通知您的活动(或任何其他上下文)有关网络请求的结果。
6.完全没有内存泄漏,就像Android Loaders一样,与Android AsyncTasks不同,它在其UI线程上通知您的活动。
7.使用简单但强大的异常处理模型。
开始使用的示例。https://github.com/octo-online/RoboSpice-samples
robospice的一个示例:https://play.google.com/store/apps/details?id=com.octo.android.robospice.motivations&feature=search_result
更新:上述答案已不再有效。请使用kotlin协程进行后台线程处理。

如何将从网络获取的数组存储到SQLite中?基本上,我想:在后台线程中获取数据并将其保存到我的SQLite中,然后通知我的UI刷新ListView。我通常使用IntentService来完成这项工作,但RoboSpice输入更少。 - Yar
@yar 如果是长时间运行的操作,您可以使用Intent Service。如果只是短时间的操作,您可以使用AsyncTask。您可以创建一个线程,在那里完成所有的工作。我已经很久没有使用Robospice了。还有其他网络库,比如Volley... - Raghunandan
好的,谢谢。我考虑使用Robospice,但它似乎不太适合我的需求(灵活性)。我长期以来一直成功地使用IntentService。 - Yar

1

如果您需要周期性地运行不同的代码,请看以下示例:

监听器:

public interface ESLThreadListener {

    public List onBackground();

    public void onPostExecute(List list);

}

线程类
public class ESLThread extends AsyncTask<Void, Void, List> {


    private ESLThreadListener mListener;

    public ESLThread() {

        if(mListener != null){

            mListener.onBackground();
        }
    }

    @Override
    protected List doInBackground(Void... params) {

        if(mListener != null){

            List list = mListener.onBackground();

            return list;
        }

        return null;
    }

    @Override
    protected void onPostExecute(List t) {
        if(mListener != null){

            if ( t != null) {
                mListener.onPostExecute(t);
            }
        }

    }


    public void setListener(ESLThreadListener mListener){

        this.mListener = mListener;
    }
}

运行不同的代码:

  ESLThread thread = new ESLThread();
                        thread.setListener(new ESLThreadListener() {
                            @Override
                            public List onBackground() {
                                List<EntityShoppingListItems>  data = RoomDB.getDatabase(context).sliDAO().getSL(fId);

                                return data;

                            }

                            @Override
                            public void onPostExecute(List t) {

                                List<EntityShoppingListItems> data = (List<EntityShoppingListItems>)t;
                                adapter.setList(data);
                            }
                        });

                        thread.execute();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接