第一行代碼讀書筆記 10 -- 探究服務(wù)(中)

本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):

  • 服務(wù)的基本用法
  • 服務(wù)的生命周期
  • 服務(wù)的其他用法
圖片來(lái)源于網(wǎng)絡(luò)

10.2 服務(wù)的基本用法

服務(wù)(Service)是 Android 中實(shí)現(xiàn)程序后臺(tái)運(yùn)行的解決方案硝训,它非常適合去執(zhí)行那些不需要和用戶交互而且還要求長(zhǎng)期運(yùn)行的任務(wù)赏寇。
??服務(wù)并不是運(yùn)行在一個(gè)獨(dú)立的進(jìn)程當(dāng)中的,而是依賴于創(chuàng)建服務(wù)時(shí)所在的應(yīng)用程序進(jìn)程斥赋。
??服務(wù)并不會(huì)自動(dòng)開啟線程,所有代碼默認(rèn)運(yùn)行在主線程中昌罩。

10.2.1 定義一個(gè)服務(wù)

在項(xiàng)目中定義一個(gè)服務(wù):在 Android Studio 中右擊 com.wonderful.myfirstcode.chapter10.service包(你項(xiàng)目所在的包名)→New→Service→Service琐驴,會(huì)彈出如下窗口:

創(chuàng)建服務(wù)的窗口

上面將服務(wù)命名為 MyService,Exported 表示是否允許除了當(dāng)前程序之外的其他程序訪問(wèn)這個(gè)服務(wù)嘀韧,Enabled 表示是否啟用這個(gè)服務(wù)篇亭。完成創(chuàng)建后的 MyService 如下:

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");
    }
}

可以看到,onBind() 方法是 Service 中唯一的一個(gè)抽象方法锄贷,必須在子類中實(shí)現(xiàn)译蒂。在服務(wù)中添加處理事情的邏輯還要重寫 Service 中的另外一些方法,如下:

public class MyService extends Service {

    . . .

    /**
     * 在服務(wù)創(chuàng)建時(shí)調(diào)用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("------MyService------", "onCreate: ");
    }

    /**
     * 在每次服務(wù)啟動(dòng)時(shí)調(diào)用
     * 若服務(wù)一旦啟動(dòng)就立刻執(zhí)行某個(gè)動(dòng)作谊却,可以將邏輯寫在此方法中
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("------MyService------", "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 在服務(wù)銷毀時(shí)調(diào)用柔昼,回收不再使用的資源
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("------MyService------", "onDestroy: ");
    }
}

另外需注意的是,每一個(gè)服務(wù)都需要在 AndroidManifest.xml 文件中進(jìn)行注冊(cè)才能生效(安卓四大組件的共有特點(diǎn))炎辨。當(dāng)然捕透,剛才創(chuàng)建服務(wù)時(shí) AS 已經(jīng)自動(dòng)幫我們注冊(cè)好了:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wonderful.myfirstcode">

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        . . .

        <service
            android:name=".chapter10.service.MyService"
            android:enabled="true"
            android:exported="true">
        </service>
    </application>

</manifest>

以上,就將一個(gè)服務(wù)定義好了。

10.2.2 啟動(dòng)和停止服務(wù)

啟動(dòng)和停止服務(wù)的方法主要是借助 Intent 來(lái)實(shí)現(xiàn)的乙嘀。下面就在項(xiàng)目中嘗試去啟動(dòng)和停止服務(wù)末购。

在布局中添加兩個(gè)按鈕,分別用于啟動(dòng)和停止服務(wù):

public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{

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

        Button start_service = (Button) findViewById(R.id.start_service);
        Button stop_service = (Button) findViewById(R.id.stop_service);
        start_service.setOnClickListener(this);
        stop_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:
                Intent startIntent = new Intent(this,MyService.class);
                startService(startIntent);// 啟動(dòng)服務(wù)
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this,MyService.class);
                stopService(stopIntent);// 停止服務(wù)
                break;
            default:
                break;
        }
    }
}

上述代碼虎谢,啟動(dòng)服務(wù) startService() 和停止服務(wù) stopService() 方法都是定義在 Context 類中的盟榴,在活動(dòng)里可以直接調(diào)用。當(dāng)然嘉冒,停止服務(wù)也可以在 MyService 的任何一個(gè)位置調(diào)用 stopSelf() 方法曹货,讓服務(wù)自己停止下來(lái)。

運(yùn)行程序讳推,點(diǎn)擊啟動(dòng)服務(wù)顶籽,打印日志如下:

啟動(dòng)服務(wù)時(shí)打印日志

點(diǎn)擊停止服務(wù),打印日志如下:

停止服務(wù)時(shí)打印日志

值得注意的是银觅,onCreate() 在服務(wù)第一次創(chuàng)建時(shí)調(diào)用礼饱,onStartCommand() 在每次啟動(dòng)服務(wù)時(shí)都會(huì)調(diào)用,上面第一次點(diǎn)擊啟動(dòng)服務(wù)時(shí)兩個(gè)方法都會(huì)執(zhí)行究驴,之后再點(diǎn)擊啟動(dòng)服務(wù)按鈕就只有 onStartCommant() 方法執(zhí)行了镊绪。

10.2.3 活動(dòng)和服務(wù)進(jìn)行通信

上面一節(jié)中,雖然服務(wù)在活動(dòng)里啟動(dòng)洒忧,但啟動(dòng)之后活動(dòng)與服務(wù)就沒(méi)什么關(guān)系了蝴韭。若要在活動(dòng)中指定服務(wù)做什么,就要借助服務(wù)里面的 onBind() 方法了熙侍。

下面舉個(gè)例子榄鉴,若在 MyService 里提供一個(gè)下載功能,然后在活動(dòng)中可以決定何時(shí)開始下載蛉抓,以及隨時(shí)查看下載進(jìn)度庆尘。實(shí)現(xiàn)這個(gè)功能的思路是創(chuàng)建一個(gè)專門的 Binder 對(duì)象來(lái)對(duì)下載功能進(jìn)行管理,如下:

public class MyService extends Service {
       
    private DownloadBinder mBinder = new DownloadBinder();
    
    class DownloadBinder extends Binder{
        // 模擬開始下載方法
        public void startDownload(){
            Log.d("------MyService------", "startDownload: ");
        }
        // 模擬查看下載進(jìn)度方法
        public int getProgress(){
            Log.d("------MyService------", "getProgress: ");
            return 0;
        }
    }

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

    . . .
}

接著在布局中添加兩個(gè)按鈕巷送,用于綁定服務(wù)和取消綁定服務(wù):

public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{

    private MyService.DownloadBinder downloadBinder;
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 服務(wù)綁定成功后調(diào)用
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 服務(wù)解除綁定后調(diào)用
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_service);

        Button bind_service = (Button) findViewById(R.id.bind_service);
        Button unbind_service = (Button) findViewById(R.id.unbind_service);
        bind_service.setOnClickListener(this);
        unbind_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){        
            case R.id.bind_service:
                Intent bindIntent = new Intent(this,MyService.class);
                // 綁定服務(wù)驶忌,三個(gè)參數(shù):Intent對(duì)象、ServiceConnection實(shí)例笑跛、標(biāo)志位
                // (BIND_AUTO_CREATE 表示活動(dòng)和服務(wù)綁定后自動(dòng)創(chuàng)建服務(wù))
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                // 解綁服務(wù)
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

上述代碼付魔,首先創(chuàng)建了一個(gè) ServiceConnection 的匿名類,里面重寫兩個(gè)方法堡牡,在 onServiceConnected() 中獲取 DownloadBinder 的實(shí)例抒抬,接下來(lái)就根據(jù)具體場(chǎng)景來(lái)調(diào)用 DownloadBinder 中的任何公共方法。

運(yùn)行程序晤柄,點(diǎn)擊綁定服務(wù),打印日志如下:

綁定服務(wù)時(shí)打印日志

點(diǎn)擊解綁服務(wù)妖胀,打印日志如下:

解綁服務(wù)時(shí)打印日志

值得注意的是芥颈,任何一個(gè)服務(wù)在整個(gè)應(yīng)用程序內(nèi)都是通用的惠勒,即 MyService 可以和任何一個(gè)活動(dòng)進(jìn)行綁定,而且綁定完成后都可以獲取到相同的 DownloadBinder 實(shí)例爬坑。

10.3 服務(wù)的生命周期

前面使用到的 onCreate()纠屋、onStartCommand()、onBind()盾计、onDestroy() 等方法都是在服務(wù)的生命周期內(nèi)可能回調(diào)的方法售担,具體如下圖所示:

服務(wù)的生命周期

值得注意的是,當(dāng)我們對(duì)一個(gè)服務(wù)既調(diào)用了 startService() 方法署辉,又調(diào)用了 bindService() 方法時(shí)族铆,要同時(shí)調(diào)用 stopService() 和 unbindService() 方法,onDestroy() 方法才會(huì)執(zhí)行哭尝。

10.4 服務(wù)的更多技巧

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

服務(wù)的優(yōu)先級(jí)較低哥攘,當(dāng)系統(tǒng)內(nèi)存不足時(shí),可能會(huì)回收正在后臺(tái)運(yùn)行的服務(wù)材鹦,若要避免被回收逝淹,可以考慮使用前臺(tái)服務(wù)。

前臺(tái)服務(wù)和普通服務(wù)的區(qū)別在于桶唐,它會(huì)一直有一個(gè)正在運(yùn)行的圖標(biāo)在系統(tǒng)的狀態(tài)欄顯示栅葡,下拉狀態(tài)欄可以看到更加詳細(xì)的信息,類似于通知的效果尤泽。

創(chuàng)建一個(gè)前臺(tái)服務(wù)如下:

public class MyService extends Service {
    . . .

   /**
     * 在服務(wù)創(chuàng)建時(shí)調(diào)用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("------MyService------", "onCreate: ");
        Intent intent = new Intent(this,MyServiceActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("這是標(biāo)題")
                .setContentText("這是內(nèi)容")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        //讓MyService變成一個(gè)前臺(tái)服務(wù)欣簇,并在系統(tǒng)狀態(tài)欄顯示出來(lái)
        startForeground(1,notification);
    }

    . . .
}

前臺(tái)服務(wù)的用法就這么簡(jiǎn)單,和創(chuàng)建通知的方法類似安吁。運(yùn)行程序醉蚁,點(diǎn)擊開啟服務(wù)或綁定服務(wù),效果如下:

前臺(tái)服務(wù)的狀態(tài)欄效果

10.4.2 使用 IntentService

之前提到過(guò)服務(wù)中的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中,若直接在服務(wù)里處理耗時(shí)操作拂酣,容易出現(xiàn) ANR(Application Not Responding)的情況通孽。

為避免上述情況,應(yīng)該在服務(wù)的每個(gè)具體的方法里開啟一個(gè)子線程滥玷,在子線程里處理耗時(shí)操作。因此一個(gè)比較標(biāo)準(zhǔn)的服務(wù)可以寫成如下形式:

public class MyService extends Service {
    . . .

   /**
     * 在每次服務(wù)啟動(dòng)時(shí)調(diào)用
     */
    @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);
    }
}

服務(wù)開啟后會(huì)一直處于運(yùn)行狀態(tài)巍棱,必須調(diào)用 stopService() 或者 stopSelf() 才能停止服務(wù)惑畴,所以要實(shí)現(xiàn)一個(gè)服務(wù)在執(zhí)行完畢后自動(dòng)停止,可以這樣寫:

 @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);
 }

當(dāng)然航徙,為了可以簡(jiǎn)單地創(chuàng)建一個(gè)異步地如贷、會(huì)自動(dòng)停止地服務(wù),Android 專門提供了一個(gè)** IntentService **類。

下面介紹下 IntentService 類的用法杠袱,創(chuàng)建一個(gè) MyIntentService 類繼承自 IntentService尚猿,如下:

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");//調(diào)用父類的有參構(gòu)造函數(shù)
    }

    /**
     * 此方法在子線程中運(yùn)行,可以處理一些具體的邏輯楣富,且不用擔(dān)心 ANR 問(wèn)題
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        // 打印當(dāng)前線程的 id
        Log.d("MyIntentService", "onHandleIntent: 線程id是 "+ Thread.currentThread().getId());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService", "onDestroy: 服務(wù)停止");
    }
}

下面舉個(gè)例子來(lái)證實(shí)下凿掂,在布局中添加個(gè)按鈕用于啟動(dòng) MyIntentService 這個(gè)服務(wù),如下:

public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{

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

        Button start_intent_service = (Button) findViewById(R.id.start_intent_service);
        start_intent_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_intent_service:
                // 打印主線程的 id
                Log.d("MyServiceActivity", "onClick: 主線程id:"+ Thread.currentThread().getId());
                Intent intentService = new Intent(this,MyIntentService.class);
                startService(intentService);
                break;
        }
    }
}

不要忘了在 AndroidManifest.xml 里注冊(cè)服務(wù)(當(dāng)然也可以用 AS 提供的快捷方式創(chuàng)建服務(wù)):

<service android:name=".chapter10.service.MyIntentService" />

運(yùn)行程序纹蝴,點(diǎn)擊按鈕庄萎,打印日志如下:

啟動(dòng)IntentService時(shí)打印日志

可以看到,MyIntentService 在運(yùn)行完畢后自動(dòng)停止了塘安。

本篇文章介紹到這糠涛,下一小節(jié)進(jìn)入服務(wù)的最佳實(shí)踐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耙旦,一起剝皮案震驚了整個(gè)濱河市脱羡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌免都,老刑警劉巖锉罐,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異绕娘,居然都是意外死亡脓规,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門险领,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)侨舆,“玉大人,你說(shuō)我怎么就攤上這事绢陌“は拢” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵脐湾,是天一觀的道長(zhǎng)臭笆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)秤掌,這世上最難降的妖魔是什么愁铺? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮闻鉴,結(jié)果婚禮上茵乱,老公的妹妹穿的比我還像新娘。我一直安慰自己孟岛,他們只是感情好瓶竭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布督勺。 她就那樣靜靜地躺著,像睡著了一般在验。 火紅的嫁衣襯著肌膚如雪玷氏。 梳的紋絲不亂的頭發(fā)上堵未,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天腋舌,我揣著相機(jī)與錄音,去河邊找鬼渗蟹。 笑死块饺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的雌芽。 我是一名探鬼主播授艰,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼世落!你這毒婦竟也來(lái)了淮腾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤屉佳,失蹤者是張志新(化名)和其女友劉穎谷朝,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體武花,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡圆凰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了体箕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片专钉。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖累铅,靈堂內(nèi)的尸體忽然破棺而出跃须,到底是詐尸還是另有隱情,我是刑警寧澤娃兽,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布菇民,位于F島的核電站,受9級(jí)特大地震影響换薄,放射性物質(zhì)發(fā)生泄漏玉雾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一轻要、第九天 我趴在偏房一處隱蔽的房頂上張望复旬。 院中可真熱鬧,春花似錦冲泥、人聲如沸驹碍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)志秃。三九已至怔球,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浮还,已是汗流浹背竟坛。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钧舌,地道東北人担汤。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像洼冻,于是被迫代替她去往敵國(guó)和親崭歧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理撞牢,服務(wù)發(fā)現(xiàn)率碾,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 原文地址:Android Service完全解析,關(guān)于服務(wù)你所需知道的一切(上) 相信大多數(shù)朋友對(duì)Service這...
    AiPuff閱讀 4,125評(píng)論 11 98
  • HandlerThread是一個(gè)Android 已封裝好的輕量級(jí)異步類撼班。HandlerThread本質(zhì)上是一個(gè)線程...
    kjy_112233閱讀 1,264評(píng)論 0 9
  • 今天的計(jì)劃是第二次在線帶讀歧匈。 但是,小妞依然在清晨發(fā)燒了砰嘁,這是連續(xù)第四天件炉,我整個(gè)人處在暈乎乎的狀態(tài),我的計(jì)劃是清晨...
    by_10閱讀 278評(píng)論 0 2
  • 風(fēng) 文/烏蒙驕子 北風(fēng)咆哮著 像是要把秋天撕碎 廣告牌終于找到自由 可以宣傳到風(fēng)的盡頭 卷簾門在怒吼...
    烏蒙驕子閱讀 239評(píng)論 0 1