Android四大組件之Service

什么是Service

  1. Service是一個應用組件, 它用來在后臺完成一個時間跨度比較大的工作且沒有關聯(lián)任何界面
  2. 一個Service可以完成下面這些工作:
    • 訪問網(wǎng)絡
    • 播放音樂
    • 文件IO操作
    • 大數(shù)據(jù)量的數(shù)據(jù)庫操作……
  3. 服務的特點:
    • Service在后臺運行,不用與用戶進行交互
    • 即使應用退出, 服務也不會停止
    • 在默認情況下晨横,Service運行在應用程序進程的主線程(UI線程)中粘捎,如果需要在Service中處理一些網(wǎng)絡連接等耗時的操作赶么,那么應該將這些任務放在分線程中處理,避免阻塞用戶界面

Service的分類

  • Local Service(本地服務):Service對象與Serive的啟動者在同個進程中運行, 兩者的通信是進程內通信秉撇,通俗點就是啟動同一應用的Service就是本地服務空盼。

  • Remote Service(遠程服務):Service對象與Service的啟動者不在同一個進程中運行, 這時存在一個進程間通信的問題, Android專門為此設計了AIDL來實現(xiàn)進程間通信饼记。通俗點就是啟動其他應用的Service就是遠程服務。

Service和Thread區(qū)別

  1. Service:

    • 用來在后臺完成一個時間跨度比較大的工作的應用組件
    • Service的生命周期方法運行在主線程, 如果Service想做持續(xù)時間比較長的工作, 需要啟動一個分線程(Thread)
    • 應用退出: Service不會停止
    • 應用再次進入: 可以與正在運行的Service進行通信
  2. Thread:

    • 用來開啟一個分線程的類, 做一個長時間的工作
    • Thread對象的run()在分線程執(zhí)行
    • 應用退出: Thread不會停止,
    • 應用再次進入: 不能再控制前面啟動的Thread對象

定義Service

  1. 定義一個類繼承于Service類

    public class MyService extends Service {
        
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
        
        ......
    }
    
  2. 在AndroidManifest.xml中配置Service

    <service android:name=".MyService">
        <intent-filter>
            <action android:name="guo.ping.activitydemo.MyService"/>
        </intent-filter>
    </service>
    

啟動荧嵌、停止Service

  • 一般啟動與停止

    context.startService(Intent intent)
    context.stopService(Intent intent)
    
  • 綁定啟動與解綁

    context.bindService(Intent intent, ServiceConnection connection)
    context.unbindService(ServiceConnection connection)
    
  • 一般啟動停止Service

    public void startMyService(View view){
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
    }
    
    public void stopMyService(View view){
        Intent intent = new Intent(this, MyService.class);
        stopService(intent);
    }
    
  • 綁定啟動Service與解綁:

    public void bindMyService(View view) {
        Intent intent = new Intent(this, MyService.class);
    
        if (mServiceConnection == null) {
            mServiceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    Log.i(TAG, "onServiceConnected()" + service.hashCode());
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    Log.i(TAG, "onServiceDisconnected()");
                }
            };
    
            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        } else {
            Log.i(TAG, "已經(jīng)綁定...");
        }
    }
    
    public void unbindMyService(View view) {
        if (mServiceConnection != null) {
            Intent intent = new Intent(this, MyService.class);
            unbindService(mServiceConnection);
    
            mServiceConnection = null;
        } else {
            Log.i(TAG, "已經(jīng)解綁...");
        }
    }
    

    MyService類重寫onBind()和onUnbind()方法:

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "MyService onBind()");
        Binder binder = new Binder();
        Log.i(TAG, "binder.hashCode() = " + binder.hashCode());
        return binder;
    }
    
    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "MyService onUnbind()");
        return super.onUnbind(intent);
    }
    

    測試結果如下呛踊,可以看到在MyService的onBind()方法中返回的Binder對象就是在onServiceConnected()回調方法中的IBinder類型的形參service。

    07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: MyService()
    07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: MyService onCreate()
    07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: MyService onBind()
    07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: 1394383824
    07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MainActivity: onServiceConnected()1394383824
    07-29 10:52:46.854 2794-2794/guo.ping.activitydemo I/MyService: MyService onUnbind()
    07-29 10:52:46.854 2794-2794/guo.ping.activitydemo I/MyService: MyService onDestroy()
    

    這種方式下啦撮,Activity和Service是通過ServiceConnection相關聯(lián)的谭网,如果Activity銷毀則這個鏈接是要斷開的,會引起ServiceConnectionLeaked異常:

    07-29 11:00:39.046 2794-2794/guo.ping.activitydemo E/ActivityThread: Activity guo.ping.activitydemo.MainActivity has leaked ServiceConnection guo.ping.activitydemo.MainActivity$1@5320601c that was originally bound here
    
    android.app.ServiceConnectionLeaked: Activity guo.ping.activitydemo.MainActivity has leaked ServiceConnection guo.ping.activitydemo.MainActivity$1@5320601c that was originally bound here
    

    需要我們在Activity的onDestory方法中進行解綁操作:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy()");
        if(mServiceConnection != null){
            Intent intent = new Intent(this, MyService.class);
            unbindService(mServiceConnection);
    
            mServiceConnection = null;
        }
    }
    

Service的生命周期

Service的生命周期
  • 當用一般方式啟動Service時赃春,如果Service已經(jīng)啟動愉择,則不會調用onCreated(),直接調用onStartCommand()方法织中;
  • 當有多個Activity等組件和一個Service進行綁定锥涕,必須所有的客戶端全部調用unbindService()方法進行解綁Service端才能調用onUnbind()方法,這一點還是合乎情理的狭吼。

AIDL

  • 每個應用程序都運行在自己的獨立進程中层坠,并且可以啟動另一個應用進程的服務,而且經(jīng)常需要在不同的進程間傳遞數(shù)據(jù)對象刁笙。
  • 在Android平臺破花,一個進程不能直接訪問另一個進程的內存空間谦趣,所以要想對話,需要將對象分解成操作系統(tǒng)可以理解的基本單元座每,并且有序的通過進程邊界前鹅。
  • 如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數(shù)峭梳。
  • AIDL (Android Interface Definition Language):用于生成可以在Android設備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼舰绘。

使用AIDL的思路理解

整個的流程其實大致是,在本進程(應用)中延赌,實現(xiàn)某一需求需要調用另一個進程(應用)的某個服務(服務中包含某個具體的方法)除盏,所以我們需要先將請求參數(shù)發(fā)給另一個進程的遠程服務,然后遠程服務將參數(shù)接收調用方法處理得出結果挫以,并將結果返回給原來的進程者蠕。而AIDL就是充當這個傳輸數(shù)據(jù)的角色,將參數(shù)送至掐松、結果傳回踱侣。


AIDL整體流程

AIDL文件的書寫規(guī)則

  1. 接口名和aidl文件名相同.
  2. 接口和方法前不用加訪問權限修飾符public,private,protected等,也不能用final,static.
  3. Aidl默認支持的類型包話java基本類型(int,long,boolean等)和(String,List,Map, CharSequence),使用這些類型時不需要import聲明.對于List和Map中的元素類型必須是Aidl支持的類型.如果使用自定義類型作為參數(shù)或返回值,自定義類型必須實現(xiàn)Parcelable接口.
  4. 自定義類型和AIDL生成的其它接口類型在aidl描述文件中,應該顯式import,即便在該類和定義的包在同一個包中.
  5. 在aidl文件中所有非Java基本類型參數(shù)必須加上in、out大磺、inout標記,以指明參數(shù)是輸入?yún)?shù)抡句、輸出參數(shù)還是輸入輸出參數(shù).
  6. Java原始類型默認的標記為int,不能為其它標記.

使用AIDL的Demo

推薦博客:

需求:

  • 傳遞倆個int類型數(shù)字a、b杠愧,調用遠程方法計算倆者之和并返回
  • 通過id來調用遠程服務查找對應的學生記錄并返回

實現(xiàn):

  1. 定義Student的JavaBean文件待榔,需要實現(xiàn)Parcelable接口。在打包與解包時流济,順序要一致锐锣,否則會出現(xiàn)錯誤;

    package guo.ping.testservice;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Student implements Parcelable {
    
        private int id;
        private String name;
        private double weight;
        
        public Student() {
        }
        
        public Student(int id, String name, double weight) {
            this.id = id;
            this.name = name;
            this.weight = weight;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public double getWeight() {
            return weight;
        }
    
        public void setWeight(double weight) {
            this.weight = weight;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", weight=" + weight +
                    '}';
        }
    
        // 添加一個靜態(tài)成員,名為CREATOR,該對象實現(xiàn)了Parcelable.Creator接口
        public static final Parcelable.Creator<Student> CREATOR = new Creator<Student>() {
            @Override
            public Student createFromParcel(Parcel source) {
                int id = source.readInt();
                String name = source.readString();
                double weight = source.readDouble();
                return new Student(id, name, weight);
            }
    
            @Override
            public Student[] newArray(int size) {
                return new Student[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        //將當前對象的屬性數(shù)據(jù)寫到Parcel包對象中(也就是打包)
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(id);
            dest.writeString(name);
            dest.writeDouble(weight);
        }
    }
    
  2. 定義Student.aidl文件绳瘟。文件名只能是Student雕憔,其他會出錯;

    package guo.ping.testservice;
    
    parcelable Student;
    
  3. 定義IAdd.aidl文件糖声,這個文件里面包含著我們需要遠程服務包含的抽象方法斤彼;

    package guo.ping.testservice;
    import guo.ping.testservice.Student;
    
    interface IAdd {
        int add(int a, int b);
        Student queryStudentById(int id);
    }
    
  4. 將上述三個文件拷貝至另一個工程,總之客戶端與服務端都需要擁有蘸泻,包名要一致琉苇,否則也是出錯;

    工程目錄結構
  5. AndroidStudio中選擇Build——>Make Project悦施,生成AIDL的代碼翁潘;

  6. 在服務端編寫Service,并實現(xiàn)AIDL文件中的抽象方法歼争;

    public class MyRemoteService extends Service {
    
        private IBinder mIBinder = new IAdd.Stub() {
            @Override
            public int add(int a, int b) throws RemoteException {
                return a + b;
            }
    
            @Override
            public Student queryStudentById(int id) throws RemoteException {
                return new Student(id, "假裝查了數(shù)據(jù)庫叫Tom", 140.5);
            }
        };
    
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mIBinder;
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            return super.onUnbind(intent);
        }
    }
    

    這里定義了成員變量mIBinder拜马,new IAdd.Stub()是Android依據(jù)我們編寫的AIDL文件生成的Java代碼里面寫好的渗勘。然后就是注冊Service,這里需要intent-filter定義好便于隱式意圖啟動俩莽。

    <service android:name=".MyRemoteService">
        <intent-filter>
            <action android:name="guo.ping.testservice.MyRemoteService"/>
        </intent-filter>
    </service>
    
  7. 客戶端編寫啟動遠程Service的方法旺坠;

    public void callTheRemoteServiceMethod(View view) throws RemoteException {
        int add = mIAdd.add(2, 5);
        Log.i(TAG, add + "");
    
        Student student = mIAdd.queryStudentById(10);
        Log.i(TAG, student.toString());
    }
    
    
    public void startRemoteService(View view) {
        Intent intent = new Intent("guo.ping.testservice.MyRemoteService");
    
        if (mServiceConnection == null) {
            mServiceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    mIAdd = IAdd.Stub.asInterface(service);
                    Log.i(TAG, "onServiceConnected()");
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    Log.i(TAG, "onServiceDisconnected()");
                }
            };
    
            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        } else {
            Log.i(TAG, "已經(jīng)綁定...");
        }
    }
    
    public void stopRemoteService(View view) {
    
        if (mServiceConnection != null) {
            unbindService(mServiceConnection);
            mIAdd = null;
            mServiceConnection = null;
        } else {
            Log.i(TAG, "已經(jīng)解綁...");
        }
    }
    
  8. 測試結果如下;

    測試結果

補充

在關閉遠程服務的時候扮超,發(fā)現(xiàn)重寫的onServiceDisconnected()方法Log不打印取刃,想著onServiceConnected()要打印必須在Service的onBind()方法中返回IBinder對象,所以便在Service中實現(xiàn)onUnbind()方法出刷,發(fā)現(xiàn)并沒有亂用璧疗。
百度了一下,原來當service所在進程crash或者被kill的時候馁龟,onServiceDisconnected才會被呼叫崩侠。具體參考:我的onServiceDisconnected為什么沒有被呼叫

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末坷檩,一起剝皮案震驚了整個濱河市却音,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矢炼,老刑警劉巖系瓢,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異句灌,居然都是意外死亡夷陋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門胰锌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肌稻,“玉大人,你說我怎么就攤上這事匕荸。” “怎么了枷邪?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵榛搔,是天一觀的道長。 經(jīng)常有香客問我东揣,道長践惑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任嘶卧,我火速辦了婚禮尔觉,結果婚禮上,老公的妹妹穿的比我還像新娘芥吟。我一直安慰自己侦铜,他們只是感情好专甩,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钉稍,像睡著了一般涤躲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贡未,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天种樱,我揣著相機與錄音,去河邊找鬼俊卤。 笑死嫩挤,一個胖子當著我的面吹牛,可吹牛的內容都是我干的消恍。 我是一名探鬼主播岂昭,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哺哼!你這毒婦竟也來了佩抹?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤取董,失蹤者是張志新(化名)和其女友劉穎棍苹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茵汰,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡枢里,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蹂午。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栏豺。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖豆胸,靈堂內的尸體忽然破棺而出奥洼,到底是詐尸還是另有隱情,我是刑警寧澤晚胡,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布灵奖,位于F島的核電站,受9級特大地震影響估盘,放射性物質發(fā)生泄漏瓷患。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一遣妥、第九天 我趴在偏房一處隱蔽的房頂上張望擅编。 院中可真熱鬧,春花似錦、人聲如沸爱态。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肢藐。三九已至故河,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吆豹,已是汗流浹背鱼的。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留痘煤,地道東北人凑阶。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像衷快,于是被迫代替她去往敵國和親宙橱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容

  • Service的生命周期 service的生命周期蘸拔,從它被創(chuàng)建開始师郑,到它被銷毀為止,可以有兩條不同的路徑: A s...
    _執(zhí)_念__閱讀 1,546評論 0 19
  • 1调窍、什么是service 一個可以在后臺執(zhí)行長時間操作而不需要用戶界面的應用組件宝冕。 需要注意的是: - 服務并不是...
    hahauha閱讀 579評論 0 1
  • 服務基本上分為兩種形式 啟動 當應用組件(如 Activity)通過調用 startService() 啟動服務時...
    pifoo閱讀 1,254評論 0 8
  • Service的相關知識雖然簡單,但是也比較瑣碎邓萨,其衍生知識也比較多地梨。本篇從Service的生命周期、運行和使用方...
    卑鄙的鹿尤菌閱讀 800評論 0 1
  • 文字有時也僅僅是為了紀念缔恳。 前天是我三十八周歲生日宝剖,請老母親和姐姐一家吃飯,選了新開業(yè)的蒸汽海鮮歉甚,據(jù)說是當下流行的...
    Marktoo閱讀 380評論 0 0