37.Android Service 及 AIDL

37.Android Service 及 AIDL


Android Service介紹

├── Context
│   ├── ContextWrapper (Service)
│   │   ├── ContextThemeWrapper (Activity)
  • Service只是一個沒有界面(Theme)的Activity,它繼承的是ContextWrapper。

  • Activity繼承的是ContextThemeWrapper,所以有主題,有界面。

Service的等級和Activity差不多。由於Service沒有界面,用戶不可見的。所以,Service一般作爲運行在後臺的服務,做一個後臺的工作(監聽什麼,記錄什麼,刷新什麼)。

說這麼多很多東西可能被概念混淆:Service雖然是不可以見的,運行的後臺的,說到底也只是一個沒有主題的Activity,但是還是運行在主線程中的。即使是Activity,不開子線程也是運行在主線程中的,所以Service也一樣,即使用了Service,要做一些耗時的操作,也必須在子線程中,或者異步任務中

要實現IPC(跨進程調用)時,Service上只能用AIDL的方式調用bindService;其次,還有使用BroadcastReceiver。


Android Service類型

  • Local Service運行在主進程中,因爲在主進程中,所以不需要IPC,那就更不需要寫一AIDL,這裏的話用bindService比較方便,綁定Service的Context銷燬的時候,再unbindService,就可以停止服務了。

  • Remote Service :在AndroidMainfest.xml的< service >標籤中指定android:process=":remote"即可。此時,Service啓動後將作爲一個獨立的進程。啓動Service的Context所在的進程被回收後,Service一樣可以運行。這樣的話,有助於不同App之間的通信。


Android Service bind 和 start

startService流程圖:

Created with Raphaël 2.1.0startService()onCreate()onStartCommand()Service was running...stopService() or stopSelf()onDestroy()service was destroyed

bindService流程圖:

Created with Raphaël 2.1.0bindService()onCreate()onBind()Service was running and it was boundunbindService()onUnbind()onDestroy()service was destroyed
  • startService

    • 就是簡單的開啓一個Service,然後服務運行,不能進行通信,除非調用stopService。

    • startService 方法被調用N次,onCreate方法只會調用一次,onStartCommand將會被調用多次(和startService的次數一致)。

  • bindService

    • 綁定Service需要一個ServiceConnection,通過這個ServiceConnection可以拿到IBinder對象,就可以和這個Service進行通信。如果要停止服務,則要使用unbindService相當於把Service的生命週期交給綁定它的Context去管理(對應的onCreate去bind,對用的onDestory去unbind)

    • 調用 bindService 調用N次,onCreate方法都只會調用一次,同時onStartCommand方法始終不會被調用

  • startService + bindService

    • 調用unbindService已經不能停止Service,必須調用stopService 或 Service自身的stopSelf。

    • 啓動又綁定Service,Service後一直會在後臺運行,當然onCreate也只會調用一次,後面的onStartCommand會被調用N次。


Local Service 實現

要做成什麼樣子呢?就做一個綁定Service,然後進行通信。然後,下載圖片,再將圖片渲染在Activity的ImageView上。

還引用了一個AsyncTask模板

先寫一個Activity的回調接口 IBinderView

public interface IBinderView {
    /**
     * 開始下載
     */
    void downloadStart();

    /**
     * 下載成功
     *
     * @param imageFilePath
     */
    void downloadSuccess(String imageFilePath);

    /**
     * 下載失敗
     */
    void downloadFailure();
}

DownloadService

public class DownloadService extends Service {

    private static final String TAG = "DownloadService";
    private IBinder binder;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return this.binder;
    }

    /**
     * Called by the system when the service is first created.  Do not call this method directly.
     */
    @Override
    public void onCreate() {
        super.onCreate();
        this.binder = new DownloadServiceBinder();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * Called by the system to notify a Service that it is no longer used and is being removed.  The
     * service should clean up any resources it holds (threads, registered
     * receivers, etc) at this point.  Upon return, there will be no more calls
     * in to this Service object and it is effectively dead.  Do not call this method directly.
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    /**
     * Service Binder
     */
    public class DownloadServiceBinder extends Binder {
        public IBinderView iBinderView;

        public DownloadService getService() {
            return DownloadService.this;
        }
    }

    public void startDownload(String imageUrl) {
        ((DownloadServiceBinder) DownloadService.this.binder).iBinderView.downloadStart();
        new DownloadImageAsyncTask(this).execute(imageUrl);
    }

    /**
     * 下載圖片異步任務
     */
    public class DownloadImageAsyncTask extends AsyncTask<String, Integer, String> {

        private Service service;
        private String localFilePath;

        public DownloadImageAsyncTask(Service service) {
            super();
            this.service = service;
        }

        /**
         * 對應AsyncTask第一個參數
         * 異步操作,不在主UI線程中,不能對控件進行修改
         * 可以調用publishProgress方法中轉到onProgressUpdate(這裏完成了一個handler.sendMessage(...)的過程)
         *
         * @param params The parameters of the task.
         * @return A result, defined by the subclass of this task.
         * @see #onPreExecute()
         * @see #onPostExecute
         * @see #publishProgress
         */
        @Override
        protected String doInBackground(String... params) {
            URL fileUrl = null;
            try {
                fileUrl = new URL(params[0]);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            if (fileUrl == null) return null;
            try {
                HttpURLConnection connection = (HttpURLConnection) fileUrl.openConnection();
                connection.setRequestMethod("GET");
                connection.setDoInput(true);
                connection.connect();

                //計算文件長度
                int lengthOfFile = connection.getContentLength();
                /**
                 * 不存在SD卡,就放到緩存文件夾內
                 */
                File cacheDir = this.service.getCacheDir();
                File downloadFile = new File(cacheDir, UUID.randomUUID().toString() + ".jpg");
                this.localFilePath = downloadFile.getPath();
                if (!downloadFile.exists()) {
                    File parent = downloadFile.getParentFile();
                    if (parent != null) parent.mkdirs();
                }
                FileOutputStream output = new FileOutputStream(downloadFile);
                InputStream input = connection.getInputStream();
                InputStream bitmapInput = connection.getInputStream();
                //下載
                byte[] buffer = new byte[1024];
                int len;
                long total = 0;
                // 計算進度
                while ((len = input.read(buffer)) > 0) {
                    total += len;
                    this.publishProgress((int) ((total * 100) / lengthOfFile));
                    output.write(buffer, 0, len);
                }
                output.close();
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * 對應AsyncTask第三個參數 (接受doInBackground的返回值)
         * 在doInBackground方法執行結束之後在運行,此時已經回來主UI線程當中 能對UI控件進行修改
         *
         * @param string The result of the operation computed by {@link #doInBackground}.
         * @see #onPreExecute
         * @see #doInBackground
         * @see #onCancelled(Object)
         */
        @Override
        protected void onPostExecute(String string) {
            super.onPostExecute(string);
            ((DownloadServiceBinder) DownloadService.this.binder).iBinderView.downloadSuccess(this.localFilePath);
        }

        /**
         * 對應AsyncTask第二個參數
         * 在doInBackground方法當中,每次調用publishProgress方法都會中轉(handler.sendMessage(...))到onProgressUpdate
         * 在主UI線程中,可以對控件進行修改
         *
         * @param values The values indicating progress.
         * @see #publishProgress
         * @see #doInBackground
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
        }

        /**
         * 運行在主UI線程中,此時是預執行狀態,下一步是doInBackground
         *
         * @see #onPostExecute
         * @see #doInBackground
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        /**
         * <p>Applications should preferably override {@link #onCancelled(Object)}.
         * This method is invoked by the default implementation of
         * {@link #onCancelled(Object)}.</p>
         * <p/>
         * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
         * {@link #doInBackground(Object[])} has finished.</p>
         *
         * @see #onCancelled(Object)
         * @see #cancel(boolean)
         * @see #isCancelled()
         */
        @Override
        protected void onCancelled() {
            super.onCancelled();
            ((DownloadServiceBinder) DownloadService.this.binder).iBinderView.downloadFailure();
        }

    }

}

DownloadServiceActivity

public class DownloadServiceActivity extends AppCompatActivity implements View.OnClickListener, IBinderView {

    private static final String OBJECT_IMAGE_URL = "https://img-blog.csdn.net/20150913233900119";

    private Button startBT;
    private ImageView imageIV;
    private DownloadService service;

    private ServiceConnection connection;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_download_service);
        this.initViews();
        this.initData();
        this.initListeners();
    }

    private void initViews() {
        TextView imageTV = (TextView) this.findViewById(R.id.image_tv);
        imageTV.setText(OBJECT_IMAGE_URL);

        this.startBT = (Button) this.findViewById(R.id.start_service_bt);
        this.imageIV = (ImageView) this.findViewById(R.id.image_iv);
    }

    private void initData() {
        this.connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                DownloadService.DownloadServiceBinder binder = (DownloadService.DownloadServiceBinder) service;
                binder.iBinderView = DownloadServiceActivity.this;
                DownloadServiceActivity.this.service = binder.getService();
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                DownloadServiceActivity.this.service = null;
            }
        };

        DownloadServiceActivity.this.bindService(
                new Intent(DownloadServiceActivity.this, DownloadService.class),
                DownloadServiceActivity.this.connection,
                Context.BIND_AUTO_CREATE
        );
    }

    private void initListeners() {
        this.startBT.setOnClickListener(this);
    }

    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_service_bt:
                this.service.startDownload(OBJECT_IMAGE_URL);
                break;
        }
    }

    /**
     * 開始下載
     */
    @Override
    public void downloadStart() {
        this.startBT.setEnabled(false);
    }

    /**
     * 下載成功
     *
     * @param imageFilePath
     */
    @Override
    public void downloadSuccess(String imageFilePath) {
        /**
         * 設置按鈕可用,並隱藏Dialog
         */
        this.startBT.setEnabled(true);
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        int screenWidth = metrics.widthPixels;
        int screenHeight = metrics.heightPixels;
        /**
         * ImageUtil.decodeScaleImage 解析圖片
         */
        Bitmap bitmap = ImageUtil.decodeScaleImage(imageFilePath, screenWidth, screenHeight);
        DownloadServiceActivity.this.imageIV.setImageBitmap(bitmap);
    }

    /**
     * 下載失敗
     */
    @Override
    public void downloadFailure() {
        this.startBT.setEnabled(true);
    }

}

AndroidManifest.xml

<service android:name="com.camnter.newlife.service.DownloadService" />

Remote Service 實現

這裏就需要寫一個AIDL文件,提供給Activity與Remote Service通訊。

IPushMessage.aidl

interface IPushMessage {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    String onMessage();

}

寫完AIDL文件後,此時,會自動生成一個 IPushMessage 接口的代碼。

IPushMessage

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/CaMnter/GitHub/AndroidLife/app/src/main/aidl/com/camnter/newlife/aidl/IPushMessage.aidl
 */
package com.camnter.newlife.aidl;
// Declare any non-default types here with import statements

public interface IPushMessage extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.camnter.newlife.aidl.IPushMessage {
        private static final java.lang.String DESCRIPTOR = "com.camnter.newlife.aidl.IPushMessage";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.camnter.newlife.aidl.IPushMessage interface,
         * generating a proxy if needed.
         */
        public static com.camnter.newlife.aidl.IPushMessage asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.camnter.newlife.aidl.IPushMessage))) {
                return ((com.camnter.newlife.aidl.IPushMessage) iin);
            }
            return new com.camnter.newlife.aidl.IPushMessage.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_basicTypes: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_onMessage: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.onMessage();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.camnter.newlife.aidl.IPushMessage {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.lang.String onMessage() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_onMessage, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_onMessage = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

    public java.lang.String onMessage() throws android.os.RemoteException;
}

可以看到:

  • 1.生成了一個靜態抽象類 Stub ,用於提供給Service實現 Service 的 IBinder,然後返回給ServiceConnection。

  • 2.生成了抽象類 Stub 的一個靜態內部類 Proxy ,作爲AIDL代理服務類,進行遠程通信。

  • 3.生成了AIDL文件中自定義的方法(basicTypes、onMessage)。

注意!!!!!!,在Service的實現中,要實現自動生成的 IPushMessage 接口中的靜態抽象類 Stub ,以此作爲 Service 的 Binder 對象,進行通信。

PushMessageService

public class PushMessageService extends Service {

    private static final String TAG = "MessageService";

    private IPushMessageImpl binder;

    /**
     * AIDL implement
     * 實現AIDL生成靜態抽象類 IPushMessage.Stub
     */
    private class IPushMessageImpl extends IPushMessage.Stub {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         *
         * @param anInt
         * @param aLong
         * @param aBoolean
         * @param aFloat
         * @param aDouble
         * @param aString
         */
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public String onMessage() throws RemoteException {
            return UUID.randomUUID().toString();
        }

    }

    /**
     * Called by the system when the service is first created.  Do not call this method directly.
     */
    @Override
    public void onCreate() {
        super.onCreate();
        this.binder = new IPushMessageImpl();
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return this.binder;
    }

}

當使用ServiceConnection橋接好Service 與 Activity成功後,我們能拿到IBinder service 對象。注意!!!!!!此時,的IBinder對象只是BinderProxy對象,不能直接轉換爲Binder對象,我們只能再調用AIDL生成類IPushMessage中的內部靜態抽象類 Stub 的靜態方法 public static com.camnter.newlife.aidl.IPushMessage asInterface(android.os.IBinder obj) 將其轉爲IPushMessage類型,以此來跟Service通信

AIDLActivity

public class AIDLActivity extends AppCompatActivity {

    private static final String TAG = "AIDLActivity";

    private TextView aidlTV;

    private String pushMessage;
    private IPushMessage iPushMessage;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /*
             * 這裏的 service 不是 Binder對象
             * 而是 BinderProxy對象
             * 不能 直接轉爲Binder( (Binder)service ),是錯誤的。
             */
            AIDLActivity.this.iPushMessage = IPushMessage.Stub.asInterface(service);
            try {
                AIDLActivity.this.pushMessage = AIDLActivity.this.iPushMessage.onMessage();
                AIDLActivity.this.aidlTV.setText(AIDLActivity.this.pushMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            AIDLActivity.this.iPushMessage = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_aidl);
        this.aidlTV = (TextView) this.findViewById(R.id.aidl_tv);
        Intent intent = new Intent(this, PushMessageService.class);
        this.startService(intent);
        this.bindService(intent, this.connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        this.unbindService(this.connection);
        super.onDestroy();
    }

}

AndroidManifest.xml

<!-- action 寫上 aidl生成類的所在包 -->
<service
    android:name=".aidl.PushMessageService"
    android:process=":remote">
    <intent-filter>
        <action android:name="com.camnter.newlife.aidl.IPushMessage" />
    </intent-filter>
</service>

然後,可以看到Service作爲單獨進程存在了:

aidl_process

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章