后臺(tái)默默的勞動(dòng)者,探究服務(wù)

服務(wù)作為Android四大組件之一默终,是一種可在后臺(tái)執(zhí)行長(zhǎng)時(shí)間運(yùn)行操作而不提供界面的應(yīng)用組件椅棺。服務(wù)可由其他應(yīng)用組件啟動(dòng),而且即使用戶切換到其他應(yīng)用穷蛹,服務(wù)仍將在后臺(tái)繼續(xù)運(yùn)行土陪。需要注意的是服務(wù)并不會(huì)自動(dòng)開(kāi)啟線程,所有的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的肴熏,所以需要在服務(wù)的內(nèi)部手動(dòng)創(chuàng)建子線程,并在這里執(zhí)行具體的任務(wù)顷窒,否則就有可能出現(xiàn)主線程被阻塞住的情況蛙吏。

Android多線程編程

異步消息機(jī)制

關(guān)于多線程編程其實(shí)和Java一致,無(wú)論是繼承Thread還是實(shí)現(xiàn)Runnable接口都可以實(shí)現(xiàn)鞋吉。在Android中需要掌握的就是在子線程中更新UI鸦做,UI是由主線程來(lái)控制的,所以主線程又稱為UI線程谓着。

Only the original thread that created a view hierarchy can touch its views.

雖然不允許在子線程中更新UI泼诱,但是Android提供了一套異步消息處理機(jī)制,完美解決了在子線程中操作UI的問(wèn)題赊锚,那就是使用Handler治筒。先來(lái)回顧一下使用Handler更新UI的用法:

public class MainActivity extends AppCompatActivity {
    private static final int UPDATE_UI = 1001;
    private TextView textView;

    private Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            if(msg.what == UPDATE_UI) textView.setText("Hello Thread!");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.tv_main);
    }

    public void updateUI(View view) {
        // new Thread(()-> textView.setText("Hello Thread!")).start(); Error!
        new Thread(()->{
            Message message = new Message();
            message.what = UPDATE_UI;
            handler.sendMessage(message);
        }).start();
    }
}
image

使用這種機(jī)制就可以出色地解決掉在子線程中更新UI的問(wèn)題,下面就來(lái)分析一下Android異步消息處理機(jī)制到底的工作原理:Android中的異步消息處理主要由4個(gè)部分組成:Message舷蒲,Handler耸袜,MessageQueue和Looper。
1牲平、Message:線程之間傳遞的消息堤框,它可以在內(nèi)部攜帶少量的信息,用于在不同線程之間交換數(shù)據(jù)。
2蜈抓、Handler:處理者启绰,它主要是用于發(fā)送和處理消息的。發(fā)送消息一般是使用Handler的sendMessage()方法沟使,而發(fā)出的消息經(jīng)過(guò)一系列地輾轉(zhuǎn)處理后酬土,最終會(huì)傳遞到Handler的handleMessage()方法中。
3格带、MessageQueue:消息隊(duì)列撤缴,它主要用于存放所有通過(guò)Handler發(fā)送的消息。這部分消息會(huì)一直存在于消息隊(duì)列中叽唱,等待被處理屈呕。每個(gè)線程中只會(huì)有一個(gè)MessageQueue對(duì)象。

4棺亭、Looper是每個(gè)線程中的MessageQueue的管家虎眨,調(diào)用Looper的loop()方法后,就會(huì)進(jìn)入到一個(gè)無(wú)限循環(huán)當(dāng)中镶摘,然后每當(dāng)發(fā)現(xiàn) MessageQueue 中存在一條消息嗽桩,就會(huì)將它取出,并傳遞到Handler的handleMessage()方法中凄敢。每個(gè)線程中也只會(huì)有一個(gè)Looper對(duì)象碌冶。

異步消息處理整個(gè)流程:首先需要在主線程當(dāng)中創(chuàng)建一個(gè)Handler 對(duì)象,并重寫(xiě)handleMessage()方法涝缝。然后當(dāng)子線程中需要進(jìn)行UI操作時(shí)扑庞,就創(chuàng)建一個(gè)Message對(duì)象,并通過(guò)Handler將這條消息發(fā)送出去拒逮。之后這條消息會(huì)被添加到MessageQueue的隊(duì)列中等待被處理罐氨,而Looper則會(huì)一直嘗試從MessageQueue 中取出待處理消息,最后分發(fā)回 Handler 的handleMessage()方法中滩援。由于Handler是在主線程中創(chuàng)建的栅隐,所以此時(shí)handleMessage()方法中的代碼也會(huì)在主線程中運(yùn)行,于是我們?cè)谶@里就可以安心地進(jìn)行UI操作了玩徊。整個(gè)異步消息處理機(jī)制的流程如下圖所示:

image

AsyncTask

不過(guò)為了更加方便我們?cè)谧泳€程中對(duì)UI進(jìn)行操作租悄,Android還提供了另外一些好用的工具,比如AsyncTask佣赖。AsyncTask背后的實(shí)現(xiàn)原理也是基于異步消息處理機(jī)制恰矩,只是Android幫我們做了很好的封裝而已。首先來(lái)看一下AsyncTask的基本用法憎蛤,由于AsyncTask是一個(gè)抽象類外傅,所以如果我們想使用它纪吮,就必須要?jiǎng)?chuàng)建一個(gè)子類去繼承它。在繼承時(shí)我們可以為AsyncTask類指定3個(gè)泛型參數(shù)萎胰,這3個(gè)參數(shù)的用途如下:

Params:在執(zhí)行AsyncTask時(shí)需要傳入的參數(shù)碾盟,可用于在后臺(tái)任務(wù)中使用。
Progress:后臺(tái)任務(wù)執(zhí)行時(shí)技竟,如果需要在界面上顯示當(dāng)前的進(jìn)度冰肴,則使用這里指定的泛型作為進(jìn)度單位。
Result:當(dāng)任務(wù)執(zhí)行完畢后榔组,如果需要對(duì)結(jié)果進(jìn)行返回熙尉,則使用這里指定的泛型作為返回值類型。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private final int REQUEST_EXTERNAL_STORAGE = 1;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void startDownload(View view) {
        verifyStoragePermissions(this);
        ProgressBar progressBar = findViewById(R.id.download_pb);
        TextView textView = findViewById(R.id.download_tv);
        new MyDownloadAsyncTask(progressBar, textView).execute("http://xxx.zip");
    }


    class MyDownloadAsyncTask extends AsyncTask<String, Integer, Boolean> {
        private ProgressBar progressBar;
        private TextView textView;

        public MyDownloadAsyncTask(ProgressBar progressBar, TextView textView) {
            this.progressBar = progressBar;
            this.textView = textView;
        }

        @Override
        protected Boolean doInBackground(String... strings) {
            String urlStr = strings[0];
            try {
                URL url = new URL(urlStr);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                InputStream inputStream = conn.getInputStream();
                // 獲取文件總長(zhǎng)度
                int length = conn.getContentLength();
                File downloadsDir = new File("...");
                File descFile = new File(downloadsDir, "xxx.zip");
                int downloadSize = 0;
                int offset;
                byte[] buffer = new byte[1024];
                FileOutputStream fileOutputStream = new FileOutputStream(descFile);
                while ((offset = inputStream.read(buffer)) != -1){
                    downloadSize += offset;
                    fileOutputStream.write(buffer, 0, offset);
                    
                    // 拋出任務(wù)執(zhí)行的進(jìn)度
                    publishProgress((downloadSize * 100 / length));
                }
                fileOutputStream.close();
                inputStream.close();
                Log.i(TAG, "download: descFile = " + descFile.getAbsolutePath());
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }

        // 在主線程中執(zhí)行結(jié)果處理
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if(aBoolean){
                textView.setText("下載完成搓扯,文件位于..xx.zip");
            }else{
                textView.setText("下載失敗");
            }
        }

        // 任務(wù)進(jìn)度更新
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            // 收到新進(jìn)度检痰,執(zhí)行處理
            textView.setText("已下載" + values[0] + "%");
            progressBar.setProgress(values[0]);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            textView.setText("未點(diǎn)擊下載");
        }
    }
}
image

1、onPreExecute():方法會(huì)在后臺(tái)任務(wù)開(kāi)始執(zhí)行之前調(diào)用锨推,用于進(jìn)行一些界面上的初始化操作铅歼,比如顯示一個(gè)進(jìn)度條對(duì)話框等。

2换可、doInBackground():方法中的所有代碼都會(huì)在子線程中運(yùn)行椎椰,我們應(yīng)該在這里去處理所有的耗時(shí)任務(wù)。任務(wù)一旦完成就可以通過(guò)return語(yǔ)句來(lái)將任務(wù)的執(zhí)行結(jié)果返回沾鳄,如果 AsyncTask的第三個(gè)泛型參數(shù)指定的是Void慨飘,就可以不返回任務(wù)執(zhí)行結(jié)果。注意洞渔,在這個(gè)方法中是不可以進(jìn)行UI操作的套媚,如果需要更新UI元素,比如說(shuō)反饋當(dāng)前任務(wù)的執(zhí)行進(jìn)度磁椒,可以調(diào)用publishProgress()方法來(lái)完成。

3玫芦、onProgressUpdate():當(dāng)在后臺(tái)任務(wù)中調(diào)用了publishProgress()方法后浆熔,onProgressUpdate()方法就會(huì)很快被調(diào)用,該方法中攜帶的參數(shù)就是在后臺(tái)任務(wù)中傳遞過(guò)來(lái)的桥帆。在這個(gè)方法中可以對(duì)UI進(jìn)行操作医增,利用參數(shù)中的數(shù)值就可以對(duì)界面元素進(jìn)行相應(yīng)的更新。

4老虫、onPostExecute():當(dāng)后臺(tái)任務(wù)執(zhí)行完畢并通過(guò)return語(yǔ)句進(jìn)行返回時(shí)叶骨,這個(gè)方法就很快會(huì)被調(diào)用。返回的數(shù)據(jù)會(huì)作為參數(shù)傳遞到此方法中祈匙,可以利用返回的數(shù)據(jù)來(lái)進(jìn)行一些UI操作忽刽,比如說(shuō)提醒任務(wù)執(zhí)行的結(jié)果天揖,以及關(guān)閉掉進(jìn)度條對(duì)話框等。

服務(wù)的基本用法

服務(wù)首先作為Android之一跪帝,自然也要在Manifest文件中聲明今膊,這是Android四大組件共有的特點(diǎn)。新建一個(gè)MyService類繼承自Service伞剑,然后再清單文件中聲明即可斑唬。

服務(wù)的創(chuàng)建與啟動(dòng)

MyService.java:

public class MyService extends Service {
    private static final String TAG = "MyService";

    public MyService() {
        
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.tim.basic_service">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

可以看到,MyService的服務(wù)標(biāo)簽中有兩個(gè)屬性黎泣,exported屬性表示是否允許除了當(dāng)前程序之外的其他程序訪問(wèn)這個(gè)服務(wù)恕刘,enabled屬性表示是否啟用這個(gè)服務(wù)。然后在MainActivity.java中啟動(dòng)這個(gè)服務(wù):

// 啟動(dòng)服務(wù)
startService(new Intent(this, MyService.class));
image

服務(wù)的停止(銷毀)

如何停止服務(wù)呢抒倚?在MainActivity.java中停止這個(gè)服務(wù):

Intent intent = new Intent(this, MyService.class);
// 啟動(dòng)服務(wù)
startService(intent);
// 停止服務(wù)
stopService(intent);

其實(shí)Service還可以重寫(xiě)其他方法:

public class MyService extends Service {
    private static final String TAG = "MyService";

    public MyService() {
    }

    // 創(chuàng)建
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    // 啟動(dòng)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    // 綁定
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    // 解綁
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.i(TAG, "unbindService: ");
    }

    // 銷毀
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}
image

其實(shí)onCreate()方法是在服務(wù)第一次創(chuàng)建的時(shí)候調(diào)用的褐着,而 onStartCommand()方法則在每次啟動(dòng)服務(wù)的時(shí)候都會(huì)調(diào)用,由于剛才我們是第一次點(diǎn)擊Start Service按鈕,服務(wù)此時(shí)還未創(chuàng)建過(guò)宫静,所以兩個(gè)方法都會(huì)執(zhí)行涨椒,之后如果再連續(xù)多點(diǎn)擊幾次 Start Service按鈕,就只有onStartCommand()方法可以得到執(zhí)行了:

image

服務(wù)綁定與解綁

在上面的例子中谴餐,雖然服務(wù)是在活動(dòng)里啟動(dòng)的,但在啟動(dòng)了服務(wù)之后呆抑,活動(dòng)與服務(wù)基本就沒(méi)有什么關(guān)系了岂嗓。這就類似于活動(dòng)通知了服務(wù)一下:你可以啟動(dòng)了!然后服務(wù)就去忙自己的事情了鹊碍,但活動(dòng)并不知道服務(wù)到底去做了什么事情厌殉,以及完成得如何。所以這就要借助服務(wù)綁定了侈咕。

比如在MyService里提供一個(gè)下載功能公罕,然后在活動(dòng)中可以決定何時(shí)開(kāi)始下載,以及隨時(shí)查看下載進(jìn)度耀销。實(shí)現(xiàn)這個(gè)功能的思路是創(chuàng)建一個(gè)專門的Binder對(duì)象來(lái)對(duì)下載功能進(jìn)行管理楼眷,修改MyService.java:

public class MyService extends Service {
    private static final String TAG = "MyService";

    private DownloadBinder mBinder = new DownloadBinder();
    
    static class DownloadBinder extends Binder {
        public void startDownload() {
            // 模擬開(kāi)始下載
            Log.i(TAG, "startDownload executed");
        }

        public int getProgress() {
            // 模擬返回下載進(jìn)度
            Log.i(TAG, "getProgress executed");
            return 0;
        }
    }

    public MyService() {}

    // 創(chuàng)建
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    // 啟動(dòng)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    // 綁定
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        return mBinder;
    }

    // 解綁
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.i(TAG, "unbindService: ");
    }

    // 銷毀
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}

MainActivity.java如下:

public class MainActivity extends AppCompatActivity {

    private MyService.DownloadBinder downloadBinder;

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void aboutService(View view) {
        int id = view.getId();
        Intent intent = new Intent(this, MyService.class);
        switch (id){
            case R.id.start_btn:
                startService(intent);
                break;
            case R.id.stop_btn:
                stopService(intent);
                break;
            case R.id.bind_btn:
                // 這里傳入BIND_AUTO_CREATE表示在活動(dòng)和服務(wù)進(jìn)行綁定后自動(dòng)創(chuàng)建服務(wù)
                bindService(intent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbind_btn:
                unbindService(connection);
                break;
        }
    }
}
image

這個(gè)ServiceConnection的匿名類里面重寫(xiě)了onServiceConnected()方法和 onServiceDisconnected()方法,這兩個(gè)方法分別會(huì)在活動(dòng)與服務(wù)成功綁定以及解除綁定的時(shí)候調(diào)用熊尉。在 onServiceConnected()方法中罐柳,通過(guò)向下轉(zhuǎn)型得到DownloadBinder的實(shí)例,有了這個(gè)實(shí)例狰住,活動(dòng)和服務(wù)之間的關(guān)系就變得非常緊密了≌偶現(xiàn)在我們可以在活動(dòng)中根據(jù)具體的場(chǎng)景來(lái)調(diào)用DownloadBinder中的任何public()方法,即實(shí)現(xiàn)了指揮服務(wù)干什么服務(wù)就去干什么的功能(雖然實(shí)現(xiàn)startDownload與getProgress實(shí)現(xiàn)很簡(jiǎn)單)催植。

需要注意的是肮蛹,任何一個(gè)服務(wù)在整個(gè)應(yīng)用程序范圍內(nèi)都是通用的勺择,即 MyService不僅可以和MainActivity綁定,還可以和任何一個(gè)其他的活動(dòng)進(jìn)行綁定蔗崎,而且在綁定完成后它們都可以獲取到相同的DownloadBinder實(shí)例酵幕。

服務(wù)的生命周期

image

一旦調(diào)用了startServices()方法,對(duì)應(yīng)的服務(wù)就會(huì)被啟動(dòng)且回調(diào)onStartCommand()缓苛,如果服務(wù)未被創(chuàng)建芳撒,則會(huì)調(diào)用onCreate()創(chuàng)建Service對(duì)象。服務(wù)被啟動(dòng)后會(huì)一直保持運(yùn)行狀態(tài)未桥,直到stopService()或者stopSelf()方法被調(diào)用笔刹。不管startService()被調(diào)用了多少次,但是只要Service對(duì)象存在冬耿,onCreate()方法就不會(huì)被執(zhí)行舌菜,所以只需要調(diào)用一次stopService()或者stopSelf()方法就會(huì)停止對(duì)應(yīng)的服務(wù)。

在通過(guò)bindService()來(lái)獲取一個(gè)服務(wù)的持久連接的時(shí)候亦镶,這時(shí)就會(huì)回調(diào)服務(wù)中的 onBind()方法日月。類似地,如果這個(gè)服務(wù)之前還沒(méi)有創(chuàng)建過(guò)缤骨,oncreate()方法會(huì)先于onBind()方法執(zhí)行爱咬。之后,調(diào)用方可以獲取到onBind()方法里返回的IBinder對(duì)象的實(shí)例绊起,這樣就能自由地和服務(wù)進(jìn)行通信了精拟。只要調(diào)用方和服務(wù)之間的連接沒(méi)有斷開(kāi),服務(wù)就會(huì)一直保持運(yùn)行狀態(tài)虱歪。

那么即調(diào)用了startService()又調(diào)用了bindService()方法的蜂绎,這種情況下該如何才能讓服務(wù)銷毀掉呢?根據(jù)Android系統(tǒng)的機(jī)制笋鄙,一個(gè)服務(wù)只要被啟動(dòng)或者被綁定了之后师枣,就會(huì)一直處于運(yùn)行狀態(tài),必須要讓以上兩種條件同時(shí)不滿足萧落,服務(wù)才能被銷毀坛吁。所以,這種情況下要同時(shí)調(diào)用stopService()和 unbindService()方法铐尚,onDestroy()方法才會(huì)執(zhí)行。

服務(wù)的更多技巧

上面講述了服務(wù)最基本的用法哆姻,下面來(lái)看看關(guān)于服務(wù)的更高級(jí)的技巧宣增。

使用前臺(tái)服務(wù)

服務(wù)幾乎都是在后臺(tái)運(yùn)行的,服務(wù)的系統(tǒng)優(yōu)先級(jí)還是比較低的矛缨,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存不足的情況時(shí)爹脾,就有可能會(huì)回收掉正在后臺(tái)運(yùn)行的服務(wù)帖旨。如果你希望服務(wù)可以一直保持運(yùn)行狀態(tài),而不會(huì)由于系統(tǒng)內(nèi)存不足的原因?qū)е卤换厥樟榉粒涂梢允褂们芭_(tái)服務(wù)解阅。比如QQ電話的懸浮窗口,或者是某些天氣應(yīng)用需要在狀態(tài)欄顯示天氣泌霍。

public class FrontService extends Service {
    String mChannelId = "1001";

    public FrontService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this, mChannelId)
                .setContentTitle("This is content title.")
                .setContentText("This is content text.")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                        R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1, notification);
    }
}
image
image

使用IntentService

服務(wù)中的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的货抄,如果直接在服務(wù)里去處理一些耗時(shí)的邏輯,就很容易出現(xiàn)ANR的情況朱转。所以需要用到多線程編程蟹地,遇到耗時(shí)操作可以在服務(wù)的每個(gè)具體的方法里開(kāi)啟一個(gè)子線程,然后在這里去處理那些耗時(shí)的邏輯藤为。就可以寫(xiě)成如下形式:

public class OtherService extends Service {
    public OtherService() {}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(()->{
            // TODO 執(zhí)行耗時(shí)操作
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
    ...
}

但是怪与,這種服務(wù)一旦啟動(dòng)之后,就會(huì)一直處于運(yùn)行狀態(tài)缅疟,必須調(diào)用stopService()或者stopSelf()方法才能讓服務(wù)停止下來(lái)分别。所以,如果想要實(shí)現(xiàn)讓一個(gè)服務(wù)在執(zhí)行完畢后自動(dòng)停止的功能存淫,就可以這樣寫(xiě):

public class OtherService extends Service {
    public OtherService() {}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(()->{
            // TODO 執(zhí)行耗時(shí)操作
            stopSelf();
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
    ...
}

雖然這種寫(xiě)法并不復(fù)雜耘斩,但是總會(huì)有一些程序員忘記開(kāi)啟線程,或者忘記調(diào)用stopSelf()方法纫雁。為了可以簡(jiǎn)單地創(chuàng)建一個(gè)異步的煌往、會(huì)自動(dòng)停止的服務(wù),Android 專門提供了一個(gè)IntentService類轧邪,這個(gè)類就很好地解決了前面所提到的兩種尷尬刽脖,下面我們就來(lái)看一下它的用法:

image

MyIntentService.java

public class MyIntentService extends IntentService {
    private static final String TAG = "MyIntentService";
    private int count = 0;
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        count++;
        Log.i(TAG, "onHandleIntent: count = " + count);
    }
}

MainActivity.java:

for (int i = 0; i < 10; i++) {
    Intent intent = new Intent(MainActivity.this, MyIntentService.class);
    startService(intent);
}
image

參考資料:《第一行代碼》

原文地址:《后臺(tái)默默的勞動(dòng)者,探究服務(wù)》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忌愚,一起剝皮案震驚了整個(gè)濱河市曲管,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硕糊,老刑警劉巖院水,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異简十,居然都是意外死亡檬某,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門螟蝙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恢恼,“玉大人,你說(shuō)我怎么就攤上這事胰默〕“撸” “怎么了漓踢?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)漏隐。 經(jīng)常有香客問(wèn)我喧半,道長(zhǎng),這世上最難降的妖魔是什么青责? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任挺据,我火速辦了婚禮,結(jié)果婚禮上爽柒,老公的妹妹穿的比我還像新娘吴菠。我一直安慰自己,他們只是感情好浩村,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布做葵。 她就那樣靜靜地躺著,像睡著了一般心墅。 火紅的嫁衣襯著肌膚如雪酿矢。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天怎燥,我揣著相機(jī)與錄音瘫筐,去河邊找鬼。 笑死铐姚,一個(gè)胖子當(dāng)著我的面吹牛策肝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隐绵,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼之众,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了依许?” 一聲冷哼從身側(cè)響起棺禾,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峭跳,沒(méi)想到半個(gè)月后膘婶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛀醉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年悬襟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拯刁。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡古胆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逸绎,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布夭谤,位于F島的核電站棺牧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏朗儒。R本人自食惡果不足惜颊乘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醉锄。 院中可真熱鬧乏悄,春花似錦、人聲如沸恳不。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烟勋。三九已至规求,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卵惦,已是汗流浹背阻肿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沮尿,地道東北人丛塌。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像畜疾,于是被迫代替她去往敵國(guó)和親赴邻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容