2诗舰、Service

2.1 Service的基本用法

定義Service

Service是Android系統(tǒng)中的四大組件之一隅肥,它是一種長生命周期的竿奏,沒有可視化界面,運(yùn)行于后臺(tái)的一種服務(wù)程序武福。
onBind()是Service類中唯一的抽象方法议双,必須在子類里實(shí)現(xiàn)。我們會(huì)在后面的小節(jié)中使用到onBind()方法捉片。
這里我們重寫了OnCreate()平痰、OnStartCommand()和OnDestroy()這3個(gè)方法,它們是每個(gè)Service最常用的3個(gè)方法了伍纫。

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

    @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();
        Log.d("MyService", "onCreate executed");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService", "onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.d("onDestroy", "onStartCommand executed");
        super.onDestroy();
    }
}

在AndroidManifest.xml中注冊

每一個(gè)Service都需要在AndroidManifest.xml中文件中進(jìn)行注冊才能生效宗雇。這是Android四大組件共有的特點(diǎn)。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.myapplication">

    <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/Theme.MyApplication">
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true">
        </service>
    </application>

</manifest>

啟動(dòng)和停止Service

創(chuàng)建MainActivity莹规,并修改OnCreate的代碼:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startServiceBtn = findViewById(R.id.button1);
        Button stopServiceBtn = findViewById(R.id.button2);
        startServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                startService(intent);
            }
        });
        stopServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                stopService(intent);
            }
        });
    }
}

startService()和stopService()方法都是定義在Context類中的赔蒲,所以我們在Activity里可以直接調(diào)用這個(gè)方法。
另外良漱,Service也可以自我停止運(yùn)行舞虱,只需要我們在Service內(nèi)部調(diào)用stopSelf()方法即可。
從Android 8.0系統(tǒng)開始母市,應(yīng)用的后臺(tái)功能被大幅削減》担現(xiàn)在只有當(dāng)應(yīng)用保持在前臺(tái)可見狀態(tài)的情況下,Service才能保證穩(wěn)定運(yùn)行患久,一旦應(yīng)用進(jìn)入后臺(tái)之后椅寺,Service隨時(shí)都有可能被系統(tǒng)回收。之所以做這樣的改動(dòng)蒋失,是為了防止許多惡意的應(yīng)用程序長期在后臺(tái)占用手機(jī)資源返帕,從而導(dǎo)致手機(jī)變得越來越卡。當(dāng)然篙挽,如果需要長期在后臺(tái)執(zhí)行一些任務(wù)荆萤,可以使用前臺(tái)Service或者WorkManager。

Activity和Service進(jìn)行通信

我們希望在MyService里提供一個(gè)下載功能嫉髓,然后在Activity中可以決定何時(shí)開始下載观腊,以及隨時(shí)查看下載進(jìn)度。
實(shí)現(xiàn)這個(gè)功能的思路是創(chuàng)建一個(gè)專門的Binder對象來對下載功能進(jìn)行管理算行。修改MyService中的代碼:

public class MyService extends Service {

    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder {
        public void startDownload() {
            Log.d("MyService", "startDownload executed");
        }

        public int getProgress() {
            Log.d("MyService", "getProgress executed");
            return 0;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    ......
}

當(dāng)Activity和Service綁定了之后梧油,就可以調(diào)用該Service里的Binder提供的方法了敦捧。修改MainActivity中的代碼:

public class MainActivity extends AppCompatActivity {

    private MyService.DownloadBinder downloadBinder;

    private 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);
        Button bindServiceBtn = findViewById(R.id.button1);
        Button stopServiceBtn = findViewById(R.id.button2);
        bindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }
        });
        stopServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                unbindService(connection);
            }
        });
    }
}

onServiceConnected()方法方法會(huì)在Activity與Service成功綁定的時(shí)候調(diào)用想括,而onServiceDisconnected()方法只有在Service的創(chuàng)建進(jìn)程崩潰或者被殺掉的時(shí)候才會(huì)調(diào)用,這個(gè)方法不太常用辙售。
Activity和Service綁定:這里我們?nèi)匀粯?gòu)建了一個(gè)Intent對象,然后調(diào)用bindService()方法將MainActivity和MyService進(jìn)行綁定骗村。bindService()方法接收3個(gè)參數(shù)嫌褪,第一個(gè)參數(shù)就是剛剛構(gòu)建出的Intent對象,第二個(gè)參數(shù)是前面創(chuàng)建出的ServiceConnection的實(shí)例胚股,第三個(gè)參數(shù)則是一個(gè)標(biāo)志位笼痛,這里傳入BIND_AUTO_CREATE表示在Activity和Service進(jìn)行綁定后自動(dòng)創(chuàng)建Service。這會(huì)使得MyService中的onCreate()方法得到執(zhí)行琅拌,但 onStartCommand()方法不會(huì)執(zhí)行缨伊。
Activity和Service解綁:調(diào)用一下unbindService()方法就可以了。
任何一個(gè)Service在整個(gè)應(yīng)用程序范圍內(nèi)都是通用的进宝,即MyService不僅可以和MainActivity綁定刻坊,還可以和任何一個(gè)其他的Activity進(jìn)行綁定,而且在綁定完成后党晋,它們都可以獲取相同的DownloadBinder實(shí)例谭胚。

2.2 Service的生命周期

2.2.1.png

一旦在項(xiàng)目的任何位置調(diào)用了Context的startService()方法,相應(yīng)的Service就會(huì)啟動(dòng)未玻,并回調(diào)onStartCommand()方法灾而。如果這個(gè)Service之前還沒有創(chuàng)建過,onCreate()方法會(huì)先于onStartCommand()方法執(zhí)行扳剿。Service啟動(dòng)了之后會(huì)一直保持運(yùn)行狀態(tài)绰疤,直到stopService()或stopSelf()方法被調(diào)用,或者被系統(tǒng)回收舞终。注意,雖然每調(diào)用一次startService()方法癣猾,onStartCommand()就會(huì)執(zhí)行一次敛劝,但實(shí)際上每個(gè)Service只會(huì)存在一個(gè)實(shí)例。所以不管你調(diào)用了多少次startService()方法纷宇,只需調(diào)用一次stopService()或stopSelf()方法夸盟,Service就會(huì)停止。
另外像捶,還可以調(diào)用Context的bindService()來獲取一個(gè)Service的持久連接上陕,這時(shí)就會(huì)回調(diào)Service中的onBind()方法。類似地拓春,如果這個(gè)Service之前還沒有創(chuàng)建過释簿,onCreate()方法會(huì)先于onBind()方法執(zhí)行。之后硼莽,調(diào)用方可以獲取到onBind()方法里返回的IBinder對象的實(shí)例庶溶,這樣就能自由地和Service進(jìn)行通信了。只要調(diào)用方和Service之間的連接沒有斷開,Service就會(huì)一直保持運(yùn)行狀態(tài)偏螺,直到被系統(tǒng)回收行疏。
當(dāng)調(diào)用了startService()方法后,再去調(diào)用stopService()方法套像。這時(shí)Service中的onDestroy()方法就會(huì)執(zhí)行酿联,表示Service已經(jīng)銷毀了。類似地夺巩,當(dāng)調(diào)用了bindService()方法后贞让,再去調(diào)用unbindService()方法,onDestroy()方法也會(huì)執(zhí)行劲够,這兩種情況都很好理解震桶。但是需要注意,我們是完全有可能對一個(gè)Service既調(diào)用了startService()方法征绎,又調(diào)用了bindService()方法的蹲姐,根據(jù)Android系統(tǒng)的機(jī)制,一個(gè)Service只要被啟動(dòng)或者被綁定了之后人柿,就會(huì)處于運(yùn)行狀態(tài)柴墩,必須要讓以上兩種條件同時(shí)不滿足,Service才能被銷毀凫岖。所以江咳,這種情況下要同時(shí)調(diào)用stopService()和unbindService()方法,onDestroy()方法才會(huì)執(zhí)行哥放。

2.3 使用前臺(tái)Service

前臺(tái)Service概念

從Android 8.0系統(tǒng)開始歼指,只有當(dāng)應(yīng)用保持在前臺(tái)可見狀態(tài)的情況下,Service才能保證穩(wěn)定運(yùn)行甥雕,一旦應(yīng)用進(jìn)入后臺(tái)之后踩身,Service隨時(shí)都有可能被系統(tǒng)回收。
而如果你希望Service能夠一直保持運(yùn)行狀態(tài)社露,就可以考慮使用前臺(tái)Service挟阻。前臺(tái)Service和普通Service最大的區(qū)別就在于,它一直會(huì)有一個(gè)正在運(yùn)行的圖標(biāo)在系統(tǒng)的狀態(tài)欄顯示峭弟,下拉狀態(tài)欄后可以看到更加詳細(xì)的信息附鸽,非常類似于通知的效果。
由于狀態(tài)欄中一直有一個(gè)正在運(yùn)行的圖標(biāo)瞒瘸,相當(dāng)于我們的應(yīng)用以另外一種形式保持在前臺(tái)可見狀態(tài)坷备,所以系統(tǒng)不會(huì)傾向于回收前臺(tái)Service。

2.3.1.png

定義前臺(tái)Service

PendingIntent和Intent有些類似情臭,都可以去指明某一個(gè)“意圖”击你,都可以用于啟動(dòng)活動(dòng)玉组、啟動(dòng)服務(wù)以及發(fā)送廣播等。不同的是丁侄,Intent更加傾向于去立即執(zhí)行某個(gè)動(dòng)作惯雳,而PendingIntent更加傾向于在某個(gè)合適的時(shí)機(jī)去執(zhí)行某個(gè)動(dòng)作。所以鸿摇,也可以把PendingIntent簡單地理解為延遲執(zhí)行的Intent石景。
PendingIntent的用法同樣很簡單,它主要提供了幾個(gè)靜態(tài)方法用于獲取PendingIntent的實(shí)例拙吉,可以根據(jù)需求來選擇是使用getActivity()方法潮孽、getBroadcast()方法、還是getService() 方法筷黔。這幾個(gè)方法所接收的參數(shù)都是相同的往史,第一個(gè)參數(shù)依舊是Context,不用多做解釋佛舱。 第二個(gè)參數(shù)一般用不到椎例,通常都是傳入0即可。第三個(gè)參數(shù)是一個(gè)Intent對象请祖,我們可以通過這個(gè)對象構(gòu)建出PendingIntent的 “意圖”订歪。第四個(gè)參數(shù)用于確定PendingIntent的行為,F(xiàn)LAG_ONE_SHOT肆捕、FLAG_NO_CREATE刷晋、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT這四種值可選,每種值的含義你可以查看文檔慎陵,通常情況下這個(gè)參數(shù)傳入0就可以了眼虱。

public class MyService extends Service {
    @Override
    public void onCreate() {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        String channelId = "my_service";
        String channelName = "前臺(tái)Service通知";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
            manager.createNotificationChannel(channel);
        }

        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this, channelId)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setSmallIcon(R.drawable.small_icon)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon))
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1, notification);
    }

    ......
}

可以看到,這里先是使用Intent表達(dá)出我們想要啟動(dòng)MainActivity的“意圖”席纽,然后將構(gòu)建好的Intent對象傳入PendingIntent的getActivity()方法里蒙幻,以得到PendingIntent的實(shí)力,接著在NotificationCompat.Builder中調(diào)用setContentIntent()方法胆筒,把它作為參數(shù)傳入即可。這樣子點(diǎn)擊一下該通知時(shí)诈豌,就會(huì)打開MainActivity的界面了仆救。
這部分代碼和創(chuàng)建通知Notification的方法類似,只不過這次在構(gòu)建Notification對象后并沒有使用NotificationManager將通知顯示出來矫渔,而是調(diào)用了StartForeground()方法彤蔽。
startForeground()方法接收兩個(gè)參數(shù):第一個(gè)參數(shù)是通知的id;第二個(gè)參數(shù)則是構(gòu)建的Notification對象庙洼。調(diào)用startForeground()方法后就會(huì)讓MyService變成一個(gè)前臺(tái)Service顿痪,并在系統(tǒng)狀態(tài)欄顯示出來镊辕。

在AndroidManifest.xml中聲明權(quán)限

另外,從Android 9.0系統(tǒng)開始蚁袭,使用前臺(tái)Service必須在AndroidManifest.xml中進(jìn)行權(quán)限聲明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication" >
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    ......
</manifest>

現(xiàn)在點(diǎn)擊“Start Service”或者“Bind Service”按鈕征懈,MyService就會(huì)以前臺(tái)Service的模式啟動(dòng)了,并且在系統(tǒng)狀態(tài)欄會(huì)顯示一個(gè)通知圖標(biāo)揩悄,下拉狀態(tài)欄可以看到通知的詳細(xì)內(nèi)容:

2.3.2.png

2.3 使用IntentService

Service處理耗時(shí)邏輯

Service中的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的卖哎,如果直接在Service里處理一些耗時(shí)的邏輯,就很容易出現(xiàn)ANR(Application Not Responding)的情況删性。
所以這個(gè)時(shí)候就需要用到Android多線程編程的技術(shù)了亏娜,我們應(yīng)該在每個(gè)Service的每個(gè)具體方法里開啟一個(gè)子線程,然后在這里處理那些耗時(shí)的邏輯:

public class MyService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 處理具體的邏輯
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    ......
}

但是蹬挺,這種Service一旦啟動(dòng)维贺,就會(huì)一直處于運(yùn)行狀態(tài),必須調(diào)用stopService()或stopSelf()方法巴帮,或者被系統(tǒng)回收溯泣,Service才會(huì)停止:

public class MyService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 處理具體的邏輯
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    ......
}

定義IntentService

總會(huì)有一些程序員忘記開啟線程,或者忘記調(diào)用stopSelf()方法晰韵。為了可以簡單地創(chuàng)建一個(gè)異步的发乔、會(huì)自動(dòng)停止的Service,Android專門提供了一個(gè)IntentService類:

public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        // 打印當(dāng)前線程的id
        Log.d("MyIntentService", "Thread id is" + Thread.currentThread().getName());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService", "onDestroy");
    }
}

不要忘記雪猪,Service都是需要再AndroidManifest.xml里注冊的栏尚。
onHandleIntent()方法可以處理一些耗時(shí)的邏輯,而不用擔(dān)心ANR的問題只恨,因?yàn)檫@個(gè)方法已經(jīng)是在子線程中運(yùn)行的了译仗。另外,這個(gè)Service在onHandleIntent()運(yùn)行結(jié)束后會(huì)自動(dòng)停止官觅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纵菌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子休涤,更是在濱河造成了極大的恐慌咱圆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件功氨,死亡現(xiàn)場離奇詭異序苏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捷凄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門忱详,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跺涤,你說我怎么就攤上這事匈睁〖嗤福” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵航唆,是天一觀的道長胀蛮。 經(jīng)常有香客問我,道長佛点,這世上最難降的妖魔是什么醇滥? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮超营,結(jié)果婚禮上鸳玩,老公的妹妹穿的比我還像新娘。我一直安慰自己演闭,他們只是感情好不跟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著米碰,像睡著了一般窝革。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吕座,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天虐译,我揣著相機(jī)與錄音,去河邊找鬼吴趴。 笑死漆诽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锣枝。 我是一名探鬼主播厢拭,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼撇叁!你這毒婦竟也來了供鸠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陨闹,失蹤者是張志新(化名)和其女友劉穎楞捂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趋厉,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寨闹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了觅廓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涵但,死狀恐怖杈绸,靈堂內(nèi)的尸體忽然破棺而出帖蔓,到底是詐尸還是另有隱情,我是刑警寧澤瞳脓,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布塑娇,位于F島的核電站,受9級特大地震影響劫侧,放射性物質(zhì)發(fā)生泄漏埋酬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一烧栋、第九天 我趴在偏房一處隱蔽的房頂上張望写妥。 院中可真熱鬧,春花似錦审姓、人聲如沸珍特。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扎筒。三九已至,卻和暖如春酬姆,著一層夾襖步出監(jiān)牢的瞬間嗜桌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工辞色, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骨宠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓淫僻,卻偏偏與公主長得像诱篷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子雳灵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

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