Android四大組件(二):Service

Service也是一個單獨的Android組件葵腹。通常用于為其他組件提供后臺服務或者監(jiān)控其他組件的運行狀態(tài)歉摧。

Activity與Service的對比

相似點

  • 都是單獨的Android組件
  • 都擁有獨立的生命周期
  • 都是Context的派生類岂傲,所以可以調(diào)用Context類定義的如getResources()痰洒、getContentResolver()等方法
  • 都擁有自己生命周期回調(diào)方法

不同點

  • Activity運行于前臺有圖形用戶界面麦向,負責與用戶交互瘟裸;Service通常位于后臺運行,不需要與用戶交互诵竭,也沒有圖形用戶界面话告。

應用場景
如果某個程序組件需要在運行時向用戶呈現(xiàn)界面,或者程序需要與用戶交互卵慰,就需要用Activity沙郭,否則就應該考慮使用Service了。


創(chuàng)建Service

  • 定義一個繼承Service的子類裳朋;
package com.rave.simpledemo.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;

/**
 * Created by Administrator on 2019/1/21.
 */
public class FirstService extends Service {
    /**
     * Service子類必須實現(xiàn)的方法病线。該方法返回一個IBinder對象,應用程序可通過IBinder對象與Service組件通信
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("===================onBind FirstService=================");
        return null;
    }

    /**
     * 當Service上綁定的所有客戶端都斷開連接時會回調(diào)該方法
     * @param intent
     * @return
     */
    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("===================onUnbind FirstService=================");
        return super.onUnbind(intent);
    }

    /**
     * Service第一次被創(chuàng)建后回調(diào)該方法
     */
    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("===================onCreate FirstService=================");
    }

    /**
     * Service被關(guān)閉之前回調(diào)該方法
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("===================onDestroy FirstService=================");
    }

    /**
     * 該方法的早期版本是onStart(Intent intent, int startId),
     * 當客戶端調(diào)用startService(Intent)方法啟動Service時都會回調(diào)該方法
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("===================onStartCommand FirstService=================");
        return super.onStartCommand(intent, flags, startId);

    }
}


  • 在Manifest.xml文件中配置該Service鲤嫡。
        <service android:name="com.rave.simpledemo.service.FirstService">
            <!-- 配置intent-filter元素說明該Service可被哪些Intent啟動 -->
            <intent-filter>
                <!-- 為intent-filter配置action -->
                <action android:name="com.rave.simpledemo.service.FIRST_SERVICE" />
            </intent-filter>
        </service>

啟動和停止Service

啟動Service
① 通過Context的startService()方法啟動Service送挑,訪問者與Service之間沒有關(guān)聯(lián),Service和訪問者之間也無法進行通信暖眼、數(shù)據(jù)交換惕耕。即使訪問者退出,Service依然運行:

        Intent intentService = new Intent(this, FirstService.class);
        intentService.setAction("com.rave.simpledemo.service.FIRST_SERVICE");
        //啟動Service
        startService(intentService);
        //停止Service
        //stopService(intentService);

特點:每當Service被創(chuàng)建時會回調(diào)onCreate方法罢荡,每次Service被啟動時都會回調(diào)onStartCommand方法赡突。多次啟動一個已有的Service組件將不會再回調(diào)onCreate方法对扶,但每次啟動時都會回調(diào)onStartCommand方法。

② 通過Context的bindService()方法啟動Service惭缰,訪問者與Service綁定在一起浪南。訪問者與Service之間可以進行方法調(diào)用或數(shù)據(jù)交換。訪問者一旦退出漱受,Service也就終止:

boolean bindService(Intent service, ServiceConnection conn,int flags);
unbindService(ServiceConnection conn)

①service:該參數(shù)通過Intent指定要啟動的Service络凿;
②conn:該參數(shù)是一個ServiceConnection對象,該對象用于監(jiān)聽訪問者與Service之間的連接情況昂羡。當訪問者與Service之間連接成功時將回調(diào)ServiceConnection對象的onServiceConnected(ComponentName name絮记,IBinder service)方法;當訪問者與Service之間斷開連接時將回調(diào)ServiceConnection對象的onServiceDisconnected(ComponentName name)方法虐先;
③flags:指定綁定時是否自動創(chuàng)建Service(如果Service還未創(chuàng)建)怨愤。該參數(shù)可指定為0(不自動創(chuàng)建)或BIND_AUTO_CREATE(自動創(chuàng)建)。

  • 如何與被綁定的Service進行本地通信(遠程的蛹批、跨進程的通信會采用AIDL撰洗,下文會單獨介紹)

ServiceConnection對象的onServiceConnected方法中有一個IBinder對象更卒,該對象可實現(xiàn)與被綁定Service之間的通信呀袱。在開發(fā)Service類時,該Service類必須提供一個IBinder onBind(Intent intent)方法帜消,在綁定本地Service的情況下猪勇,onBind(Intent intent)方法所返回的IBinder對象將會傳給ServiceConnection對象里onServiceConnected(ComponentName name, IBinder service)方法的service參數(shù)设褐,訪問者就可通過該IBinder對象與Service進行通信。在實際開發(fā)中通常會采用繼承Binder(IBinder的實現(xiàn)類)的方式實現(xiàn)自己的IBinder對象泣刹。

具體步驟:
①在Service里面創(chuàng)建一個IBinder對象(通過繼承Binder類來實現(xiàn)自定義的Binder)助析;
②在onBind(Intent intent)方法里面返回這個Binder對象;
③客戶端在綁定Service時所需的ServiceConnection對象的onServiceConnected方法里面獲取到onBind方法返回的Binder對象项玛;
④客戶端通過持有的Binder對象來訪問Service貌笨。


通過Binder通信

對于Service的onBind()方法所返回的IBinder對象來說,它可被當成該Service組件所返回的回調(diào)對象襟沮,Service允許客戶端通過該IBinder對象來訪問Service內(nèi)部的數(shù)據(jù),這樣即可實現(xiàn)客戶端與Service之間的通信昌腰。

package com.rave.simpledemo.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;

/**
 * Created by Administrator on 2019/1/22.
 */
public class BindService extends Service {
    private boolean quit = false;
    private int count = 0;
    //定義onBinder方法要返回的Binder對象
    private MyBinder binder = new MyBinder();
    // 通過繼承Binder來實現(xiàn)IBinder類
    public class MyBinder extends Binder {

        public int getCount() {
            return count;
        }
    }
    
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("==================BindService onBind====================");
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("==================BindService onUnbind====================");
        return true;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("==================BindService onCreate====================");
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!quit) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++;
                }
                stopSelf();
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.quit = true;
        System.out.println("==================BindService onDestroy====================");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("===================BindService onStartCommand=================");
        return super.onStartCommand(intent, flags, startId);

    }
}
package com.rave.simpledemo.service;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.rave.simpledemo.R;

public class BindServiceActivity extends AppCompatActivity {

    private Button btnStartService;
    private Button btnStopService;
    private Button btnGetStatus;

    //啟動Service時返回的IBinder對象
    BindService.MyBinder binder;
    //定義一個ServiceConnection對象
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            System.out.println("===================BindService onServiceConnected=================");
            binder = (BindService.MyBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            System.out.println("===================BindService onServiceDisconnected=================");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bind_service);
        btnStartService = (Button) findViewById(R.id.btn_startservice);
        btnStopService = (Button) findViewById(R.id.btn_stopservice);
        btnGetStatus = (Button) findViewById(R.id.btn_getstatus);

        final Intent intent = new Intent(BindServiceActivity.this, BindService.class);
        intent.setAction("com.rave.simpledemo.service.BIND_SERVICE");

        btnStartService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //綁定指定的Service
                bindService(intent, conn, Service.BIND_AUTO_CREATE);
            }
        });

        btnStopService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //解除綁定Service(這里的參數(shù)ServiceConnection對象)
                unbindService(conn);
            }
        });

        btnGetStatus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int count = binder.getCount();
                Toast.makeText(BindServiceActivity.this, count + "", Toast.LENGTH_LONG).show();
            }
        });
    }

}

Service的生命周期

上文的啟動Service的兩個示例中开伏,我們都有對Service的生命周期方法進行觀測。我們發(fā)現(xiàn)根據(jù)啟動方式的不同Service回調(diào)的生命周期方法也不同遭商。

Service的生命周期

總結(jié)一下Service的生命周期方法回調(diào):

startService()和bindService()是兩個獨立的操作固灵,我們可以只啟動不綁定;也可以通過bindService()方法的第三個參數(shù)劫流,在綁定時啟動巫玻;還可以先啟動后綁定丛忆;
①被啟動的服務的生命周期:如果一個Service被某個Activity調(diào)用Context.startService方法啟動(Service也可以啟動服務),那么不管是否有Activity使用bindService綁定或者unbindService解除綁定該Service仍秤,該Service都在后臺運行熄诡。如果一個Service被startService方法多次啟動,那么onCreate方法只會調(diào)用一次诗力,但是onStartCommand將會每次都被回調(diào)凰浮,并且系統(tǒng)只會創(chuàng)建Service的一個實例(因此你只需要調(diào)用一次stopService來停止)。該Service將會一直在后臺運行苇本,而不管對應程序的Activity是否在運行袜茧,直到被調(diào)用stopService,或自身的stopSelf方法瓣窄。當然如果系統(tǒng)資源不足笛厦,Android系統(tǒng)也可能結(jié)束服務。

②被綁定的服務的生命周期:如果一個Service被某個Activity調(diào)用Context.bindService方法綁定啟動俺夕,不管調(diào)用bindService調(diào)用幾次裳凸,onCreate方法都只會調(diào)用一次,同時onStartCommand方法始終不會調(diào)用啥么。當鏈接建立之后登舞,Service將會一直運行,除非調(diào)用Context.unbindService斷開連接或者之前調(diào)用bindService的Context不存在了(如Activity被finish的時候)悬荣,系統(tǒng)將會自動停止Service菠秒,對應onDestroy將被調(diào)用。

③被啟動又被綁定的服務的生命周期:如果一個Service先被啟動氯迂,后又被綁定践叠,則該Service將會一直在后臺運行。并且不管如何調(diào)用嚼蚀,onCreate始終只會調(diào)用一次禁灼,對應startService調(diào)用多少次,Service的onStartCommand就會調(diào)用多少次轿曙。調(diào)用unbindService將不會停止Service弄捕,而必須調(diào)用stopService或Service自身的stopSelf來停止服務(在沒有解綁的前提下使用stopService是無法停止服務的)。

④當服務被停止時的清除工作:當一個Service被終止(1导帝、調(diào)用stopService守谓;2、調(diào)用stopSelf您单;3斋荞、不再有綁定的連接(通過bindService啟動))時,onDestroy方法將會被回調(diào)虐秦,在這里應當做一些清除工作(如停止Service中創(chuàng)建并運行的線程平酿、注冊的偵聽器凤优、接收器等)。

使用Service時的注意事項

①在調(diào)用bindService綁定到Service的時候蜈彼,就應當保證在某處調(diào)用unbindService解除綁定(盡管Activity被finish的時候會自動解除綁定筑辨,并且會自動停止Service);

②使用startService啟動服務之后柳刮,一定要使用stopService停止服務挖垛,不管你是否使用bindService;

③同時使用startService和bindService時要注意:Service的終止秉颗,需要unbindService與stopService同時調(diào)用痢毒,才能終止Service。關(guān)于調(diào)用順序蚕甥,如果先調(diào)用unbindService此時服務不會自動終止哪替,再調(diào)用stopService服務才會停止;如果先調(diào)用stopService此時服務也不會終止菇怀,需要再調(diào)用unbindService(或者調(diào)用bindService的Context不存在了凭舶,如Activity被finish的時候)服務才會自動停止。

④當旋轉(zhuǎn)手機屏幕的時候爱沟,如果發(fā)生了Activity的重建(請參考Android四大組件(一)Activity)帅霜,旋轉(zhuǎn)之前的使用bindService建立的連接便會斷開(Context不存在了)。

⑤在sdk 2.0之前的版本呼伸,使用的onStart方法被onStartCommand方法替換了身冀,但是onStart方法仍然有效。


跨進程調(diào)用Service(AIDL服務)

理解什么是AIDL(Android Interface Definition Language,即Android接口定義語言)
Android系統(tǒng)中括享,應用程序都運行在自己的進程中搂根,進程之間一般無法直接進行數(shù)據(jù)交換。在Java技術(shù)中铃辖,RMI可實現(xiàn)跨進程調(diào)用剩愧;在Android的中,采用了相似的方式來實現(xiàn)跨進程調(diào)用Service娇斩,這就是AIDL仁卷。與RMI相似,也是先定義一個遠程調(diào)用接口犬第,然后為該接口提供一個實現(xiàn)類五督;與RMI不同的是,客戶端訪問Service時瓶殃,Android并不是直接返回Service對象給客戶端。這點在綁定本地Service時已經(jīng)看到副签,Service只是將一個回調(diào)對象(IBinder對象)通過onBind()方法返回給客戶端遥椿。因此Android的AIDL遠程接口的實現(xiàn)類就是IBinder實現(xiàn)類基矮。與綁定本地Service不同的是,本地Service的onBind()方法會直接把IBinder對象本身傳給客戶端的ServiceConnection的onServiceConnected方法的第二個參數(shù)冠场。遠程Service的onBind方法只是將IBinder對象的代理傳給客戶端的ServiceConnection的onServiceConnected方法的第二個參數(shù)家浇。客戶端獲取了遠程Service的IBinder對象的代理后碴裙,就可通過該IBinder對象去回調(diào)遠程Service的屬性或方法了钢悲。

AIDL的操作步驟

Service端
1、新建AIDL文件

新建AIDL文件

2舔株、按需求自己定義接口


自己定義IUserInfo接口

3莺琳、重新build工程,AS會在build目錄中生成aidl對應的java實現(xiàn)


4载慈、編寫Service類

package com.rave.simpledemo.aidldemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import java.util.Timer;
import java.util.TimerTask;

/**
 * Created by Administrator on 2019/1/25.
 */
public class AidlUserService extends Service {
    private String name = "rave";
    private int age = 0;

    private int count = 0;
    private Timer timer = new Timer();

    /**
     * 這里跟本地Service不一樣惭等,沒有直接繼承Binder類,而是繼承了ADT所生成的IUserInfo.Stub
     * 但是IUserInfo.Stub也是Binder的子類
     */
    private IBinder stub = new IUserInfo.Stub() {

        @Override
        public String getName() throws RemoteException {
            return name + count;
        }

        @Override
        public int getAge() throws RemoteException {
            return age + count;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        /**返回catBinder對象
         * 在綁定本地Service的情況下办铡,該catBinder對象會直接傳給客戶端的
         * ServiceConnection對象的onServiceConnected方法的第二個參數(shù)辞做;
         * 在綁定遠程Service的情況下,只將catBinder對象的代理傳給客戶端的
         * ServiceConnection對象的onServiceConnected方法的第二個參數(shù)寡具。
         */
        return stub;
    }

    @Override
    public void onCreate() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                count++;
            }
        }, 0, 1 * 1000);
    }

    @Override
    public void onDestroy() {
        /**
         * 在onDestroy里面做清理回收工作
         */
        timer.cancel();
    }
}

5秤茅、當然不要忘記在manifest.xml里注冊該Service

        <service android:name=".aidldemo.AidlUserService">
            <intent-filter>
                <action android:name="com.rave.simpledemo.service.AIDL_USER_SERVICE" />
            </intent-filter>
        </service>

客戶端訪問AIDL服務
1、將Service端的AIDL接口文件復制到客戶端應用中童叠,要連文件路徑一起復制

需要復制的部分

2框喳、同Service端一樣,重新build工程拯钻,AS會調(diào)用ADT在build目錄中生成aidl對應的java實現(xiàn)

3帖努、創(chuàng)建ServiceConnection對象,并以ServiceConnection對象作為參數(shù)粪般,調(diào)用bindService()方法綁定遠程Service

package com.richinfo.aidlclient;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.rave.simpledemo.aidldemo.IUserInfo;

public class UserInfoActivity extends AppCompatActivity {

    private TextView tvUserName, tvUserAge;
    private Button btnBind, btnUnbind, btnGet;
    private Intent intent;

    private IUserInfo userInfoService;
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             * 與本地綁定Service不同拼余,綁定遠程Service的ServiceConnection不能直接獲取Service的onBind方法所返回的對象;
             * 它只能返回onBind()方法所返回的對象的代理亩歹,因此需要在這里做asInterface的處理匙监。
             */
            userInfoService = IUserInfo.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_info);
        tvUserName = (TextView) findViewById(R.id.tv_username);
        tvUserAge = (TextView) findViewById(R.id.tv_userage);
        btnBind = (Button) findViewById(R.id.btn_bind);
        btnUnbind = (Button) findViewById(R.id.btn_unbind);
        btnGet = (Button) findViewById(R.id.btn_get);

        intent = new Intent();
        // Android 5.0以后需要用顯示意圖來啟動Service,否則會報異常
        // java.lang.IllegalArgumentException: Service Intent must be explicit
        // 這里由于是跨進程所以用Component來解決這個問題,試過用setPackage的方式小作,無法解決這個問題
        intent.setComponent(new ComponentName("com.rave.simpledemo", "com.rave.simpledemo.aidldemo.AidlUserService"));
        //這種方式很多博客上說可以亭姥,經(jīng)測試這種方式可以startService成功的啟動遠程service,但是在啟動之后再綁定會出現(xiàn)綁定失敗的情況顾稀。
        //intent.setPackage("com.rave.simpledemo");

        intent.setAction("com.rave.simpledemo.service.AIDL_USER_SERVICE");
        btnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(intent);
                bindService(intent, conn, Service.BIND_AUTO_CREATE);
            }
        });

        btnUnbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });

        btnGet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    tvUserName.setText(userInfoService.getName());
                    tvUserAge.setText(userInfoService.getAge()+"");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //記得解綁
        unbindService(conn);
        stopService(intent);
    }
}

實踐中遇到的問題
1达罗、Android 5.0以后系統(tǒng)禁止使用隱式意圖來啟動Service,所以在跨進程特別是跨APP的綁定Service的時候,可以采用如下方式:

        Intent intent = new Intent();
        intent.setAction("com.rave.simpledemo.service.AIDL_USER_SERVICE");
        // Android 5.0以后需要用顯示意圖來啟動Service粮揉,否則會報異常
        // java.lang.IllegalArgumentException: Service Intent must be explicit
        // 這里由于是跨進程所以用Component來解決這個問題,試過用setPackage的方式巡李,無法解決這個問題
        intent.setComponent(new ComponentName("com.rave.simpledemo", "com.rave.simpledemo.aidldemo.AidlUserService"));

        //這種方式很多博客上說可以,經(jīng)測試這種方式可以startService成功的啟動遠程service扶认,但是在啟動之后再綁定會出現(xiàn)綁定失敗的情況侨拦。
        //intent.setPackage("com.rave.simpledemo");

2、無法通過bindService設置BIND_AUTO_CREATE來創(chuàng)建服務辐宾,只能先startService啟動狱从,再調(diào)用bindService綁定:

        // 在使用AIDL的時候,必須要先用startService來啟動服務叠纹,再用bindService來綁定服務季研,否則會綁定失敗。
        startService(intent);
        boolean flag = bindService(intent, conn, Service.BIND_AUTO_CREATE);

3吊洼、在Service的onDestroy里面一定要清理資源训貌,避免內(nèi)存泄漏。已經(jīng)客戶端調(diào)用bindService之后要手動解綁冒窍。

傳遞復雜數(shù)據(jù)的AIDL服務

上面我們所展示的都是通過AIDL傳輸基本數(shù)據(jù)類型递沪。如果是自定義類的話,Android要求調(diào)用遠程Service的參數(shù)和返回值都必須實現(xiàn)Parcelable接口综液。具體步驟如下:

服務端
1款慨、使用AIDL代碼來定義這些自定義類型(包括參數(shù)類型和返回值類型):

使用AIDL定義參數(shù)類型
使用AIDL定義返回值類型

2、定義實現(xiàn)Parcelable接口的參數(shù)類和返回值類(返回值類一樣這里demo省略代碼)

package com.rave.simpledemo.aidldemo;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by Administrator on 2019/1/28.
 */
public class Person implements Parcelable {
    private Integer id;
    private String name;
    private String pass;

    public Person() {

    }

    public Person(Integer id, String name, String pass) {
        super();
        this.id = id;
        this.name = name;
        this.pass = pass;
    }

    protected Person(Parcel in) {
        name = in.readString();
        pass = in.readString();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getPass() {
        return pass;
    }

    public void setPass(String pass) {
        this.pass = pass;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((pass == null) ? 0 : pass.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        Person other = (Person) obj;
        if (this.name == null) {
            if (other.name != null) {
                return false;
            }
        } else if (!this.name.equals(other.name)) {
            return false;
        }

        if (this.pass == null) {
            if (other.pass != null) {
                return false;
            }
        } else if (!this.pass.equals(other.pass)) {
            return false;
        }

        return true;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //把對象所包含的數(shù)據(jù)寫到Parcel中
        dest.writeInt(id);
        dest.writeString(name);
        dest.writeString(pass);
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in.readInt(), in.readString(), in.readString());
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
}

3谬莹、使用AIDL定義通信接口

AIDL定義通信接口
  • 在AIDL接口中定義方法時檩奠,需要指定形參的傳遞模式。對于java語言來說附帽,一般都是采用傳入?yún)?shù)的方式埠戳,因此上面指定為in模式。具體的傳參方式有以下幾種:
    ① in:參數(shù)由客戶端設置蕉扮,或者理解為客戶端傳入?yún)?shù)值整胃;
    ② out:參數(shù)由服務器設置,或者理解成由服務端返回值喳钟;
    ③ inout:客戶端服務端都可以設置屁使,或者理解成可以雙向通信。

  • 任何自定義類型都需要在這里顯示使用import引入(即便該類的AIDL文件和通信接口是在同一個包下)奔则,否則會報錯:

Error:Execution failed for task ':app:compileDebugAidl'.
com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'G:\Android\sdk1\build-tools\23.0.1\aidl.exe'' finished with non-zero exit value 1

4蛮寂、再次不要忘記在manifest.xml里注冊該Service

客戶端
1、跟傳輸基本類型一樣易茬,將Service端的AIDL接口文件復制到客戶端應用中(包括定義接口的aidl的文件IPet.aidl酬蹋、定義的自定義類型的AIDL文件(包括參數(shù)類型和返回值類型)、定義的自定義類型的Java類文件(包括參數(shù)類型和返回值類型))。同樣要保證文件路徑和類的包名和跟Service端一致除嘹。

復制到客戶端的文件

2写半、后面的啟動服務和綁定服務的操作跟傳輸基本數(shù)據(jù)類型一樣。下面給出本例的客戶端代碼:

package com.richinfo.aidlclient;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.rave.simpledemo.aidldemo.IPet;
import com.rave.simpledemo.aidldemo.Person;
import com.rave.simpledemo.aidldemo.Pet;

import java.util.List;

public class ComplexTypeActivity extends AppCompatActivity {
    private TextView tvContent;
    private Button btnBind;
    private Button btnUnbind;
    private Button btnGet;

    private Intent complexServiceIntent;
    private IPet iPetService;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iPetService = IPet.Stub.asInterface(service);
            System.out.println("onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

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

        tvContent = (TextView) findViewById(R.id.tv_content);
        btnBind = (Button) findViewById(R.id.btn_bind);
        btnGet = (Button) findViewById(R.id.btn_get);
        btnUnbind = (Button) findViewById(R.id.btn_unbind);

        complexServiceIntent = new Intent();
        complexServiceIntent.setAction("com.rave.simpledemo.service.COMPLEX_SERVICE");
        complexServiceIntent.setComponent(new ComponentName("com.rave.simpledemo", "com.rave.simpledemo.aidldemo.ComplexService"));

        btnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(complexServiceIntent);
                boolean isSuccess = bindService(complexServiceIntent, conn, Service.BIND_AUTO_CREATE);
                System.out.println("bind successed:" + isSuccess);
            }
        });

        btnGet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    List<Pet> petList = iPetService.getPets(new Person(1, "rava", "rave"));
                    int size = petList.size();
                    tvContent.setText("pet count:" + size + "");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        btnUnbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
        stopService(complexServiceIntent);
    }
}

獲取系統(tǒng)服務

1尉咕、 電話管理器(TelephonyManager):

//getSystemService(String name):根據(jù)服務名稱來獲取系統(tǒng)服務。
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
  • 獲取網(wǎng)絡狀態(tài)
  • 獲取SIM卡信息
  • 監(jiān)聽通話狀態(tài)

2璃岳、短信管理器(SmsManager):

//獲取SmsManager
SmsManager smsManager = SmsManager.getDefault();
  • 短信發(fā)送

3年缎、音頻管理器(AudioManager):

//獲取音頻管理器
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
  • 調(diào)節(jié)系統(tǒng)音量:adjustStreamVolume(int streamType, int direction, int flags),streamType聲音類型铃慷;direction對聲音進行增大還是減械ノ摺;flags調(diào)整聲音時的標志犁柜。
  • 設置麥克風靜音:setMicrophoneMute(boolean on)洲鸠。
  • 設置聲音模式:setMode(int mode),可設置的值有NORMAL馋缅、RINGTONE和IN_CALL扒腕。
  • 設置手機的電話鈴聲模式:setRingerMode(int ringerMode),可設置鈴聲模式萤悴、靜音模式瘾腰、振動模式。
  • 設置打卡擴音器:setSpeakerphoneOn(boolean on)覆履。
  • 將制定類型的聲音調(diào)整為靜音:setStreamMute(int streamType, boolean state)蹋盆,streamType聲音類型。
  • 設置手機指定類型的音量值:setStreamVolume(int streamType, int index, int flags)硝全,streamType聲音類型栖雾;index具體的音量值;flags調(diào)整聲音時的標志伟众。

4析藕、振動器(Vibrator):

Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
  • 控制手機振動:vibrate(long milliseconds)、vibrate(long[] pattern, int repeat)振動赂鲤;cancel()關(guān)閉振動噪径。

5、手機鬧鐘服務(AlarmManager):

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
  • 開發(fā)鬧鐘應用
  • 全局定時器

特殊的Service——IntentService

通過上面的內(nèi)容数初,我們已經(jīng)發(fā)現(xiàn)了找爱。服務的代碼默認運行在主線程里的。如果要在服務里面執(zhí)行耗時操作的代碼泡孩,就需要開啟一個主線程去處理這些代碼车摄。比如在啟動和停止Service的例子中:

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("==================BindService onCreate====================");
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!quit) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++;
                }
                stopSelf();
            }
        }).start();
    }

我們知道服務一旦啟動就會一直運行下去,必須手動調(diào)用stopService()或者stopSelf()方法才能讓服務停止下來。所以在上例中我們需要在run()方法的最后吮播,調(diào)用stopSelf來結(jié)束服務变屁。谷歌給我們提供IntentService,就是為了方便開發(fā)者處理在Service中開啟子線程的情況意狠。它幫我們開發(fā)者封裝了開啟子線程粟关、線程間通信(采用Handler)、以及自動銷毀服務等操作环戈。

IntentService的使用步驟
1闷板、繼承IntentService創(chuàng)建自定義的IntentService類

package com.rave.simpledemo.service;

import android.app.IntentService;
import android.content.Intent;

/**
 * Created by Administrator on 2019/1/30.
 */
public class BindIntentService extends IntentService {
    private boolean quit = false;
    private int count = 0;

    public BindIntentService() {
        super("BindIntentService");
    }

    /**
     * 必須實現(xiàn)的抽象方法,我們的業(yè)務邏輯就是在這個方法里面去實現(xiàn)的
     * 方法在子線程運行院塞,我們不用去關(guān)心ANR的問題
     * 在OnCreate方法里面創(chuàng)建并啟動子線程遮晚,
     * 在OnStartCommand方法里面,將Intent封裝成Message并傳遞到子線程的handler拦止,然后回調(diào)onHandleIntentonStart
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        while (!quit) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println("==================Current count:" + count + "====================");
            if (count == 10)
                quit = true;
        }
    }

    /**
     * 為了驗證onHandleIntent執(zhí)行后县遣,服務會不會自動銷毀,我們在這里重寫onDestroy方法
     * 如果會自動銷毀汹族,那么在"IntetnService Running"出現(xiàn)后萧求,應該會出現(xiàn)"IntetnService Stop"
     */
    @Override
    public void onDestroy() {
        System.out.println("==================BindIntentService onDestroy:" + count + "====================");
        //經(jīng)測試,如果onHandleIntent里面的代碼邏輯沒有走完鞠抑,在服務外部調(diào)用stopService來停止服務饭聚,并不會立即結(jié)束子線程
        quit = true;
        super.onDestroy();
    }
}

2、在Manifest里面注冊IntentService搁拙,注冊方式跟Service一樣

3秒梳、啟動IntentService
我們推薦使用startService(intent)的方式來啟動服務。

注意!!!也可以使用bindService的方式來啟動服務箕速。但是在使用bindService的啟動的時候酪碘,即使onHandleIntent里面的邏輯執(zhí)行完畢,也不會自動銷毀服務盐茎。原因應該是Service還是被綁定狀態(tài)兴垦,調(diào)用stopSelf無法停止。所以如果使用bindService啟動服務將會失去IntentService的一大特點字柠,使用時請謹慎.

4探越、銷毀IntentService
IntentService的一大特點就是onHandleIntent里面的代碼邏輯執(zhí)行完之后,自動銷毀Service窑业。所以我們可以不用專門做停止IntentService的操作钦幔。

注意!!!我們也可以在客戶端手動調(diào)用stopService來銷毀服務,但是用這種方式不會停止IntentService里面啟動的子線程常柄。如果要采用這種方式銷毀服務鲤氢,一定要注意子線程無法停止搀擂,從而導致內(nèi)存泄漏的問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卷玉,一起剝皮案震驚了整個濱河市哨颂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌相种,老刑警劉巖威恼,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚂子,居然都是意外死亡沃测,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門食茎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人馏谨,你說我怎么就攤上這事别渔。” “怎么了惧互?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵哎媚,是天一觀的道長。 經(jīng)常有香客問我喊儡,道長拨与,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任艾猜,我火速辦了婚禮买喧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘匆赃。我一直安慰自己淤毛,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布算柳。 她就那樣靜靜地躺著低淡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞬项。 梳的紋絲不亂的頭發(fā)上蔗蹋,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音囱淋,去河邊找鬼猪杭。 笑死,一個胖子當著我的面吹牛绎橘,可吹牛的內(nèi)容都是我干的胁孙。 我是一名探鬼主播唠倦,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涮较!你這毒婦竟也來了稠鼻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狂票,失蹤者是張志新(化名)和其女友劉穎候齿,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闺属,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡慌盯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掂器。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亚皂。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖国瓮,靈堂內(nèi)的尸體忽然破棺而出灭必,到底是詐尸還是另有隱情,我是刑警寧澤乃摹,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布禁漓,位于F島的核電站,受9級特大地震影響孵睬,放射性物質(zhì)發(fā)生泄漏播歼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一掰读、第九天 我趴在偏房一處隱蔽的房頂上張望秘狞。 院中可真熱鬧,春花似錦磷支、人聲如沸谒撼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽廓潜。三九已至,卻和暖如春善榛,著一層夾襖步出監(jiān)牢的瞬間辩蛋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工移盆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留悼院,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓咒循,卻偏偏與公主長得像据途,于是被迫代替她去往敵國和親绞愚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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