程序员

AndroidAIDL实现IPC服务与通告实战

2018-04-08  本文已影响0人  坚果jimbowhy

本文示例代码打包下载含apk

github resource code project
IPC Service apk download
IPC Cloent apk download

一、基本概念

Service服务是Android系统最常用的四大部件之一,Android支持Service服务的原因主要目的有两个,一是简化后台任务的实现,二是实现在同一台设备当中跨进程的远程信息通信。

Service服务主要分为LocalService本地服务与RemoteService远程服务两种,本地服务只支持同一进程内的应用程序进行访问,远程服务可通过AIDL(AndroidInterfaceDefinitionLanguage)技术支持跨进程访问。服务可以通过Context.startService()和Context.bindService()进行启动,一般LocalService本地服务可使用其中一种方法启动,但RemoteService远程服务只能使用Context.bindService()启动,而两种调用方式在使用场景与活动流程中都存在差异。还有通过多线程技术处理Service服务的延时操作等技术,下文将针对Android系统的Service服务的一系列操作进行深入探讨。

本文要结合android通告notification和服务来开发一个区有实用功能的应用。通告是android系统的重要ui功能,是用户经常参与又非常便利的互动环节,应用可以通过向通知栏发送通告信息,来向用户传达程序信息,通过定制通告视图还可以方便用户对应用的操控,如在通知栏展示播放器的功能键,后面结合代码解说。

2、Service服务的类型

2.1按照Service的生命周期模型一共分为两种类型

第一类是直接通过Context.startService()启动,通过Context.stopService()结束Service,其特点在于调用简单,方便控制。缺点在于一旦启动了Service服务,除了再次调用或结束服务外就再无法对服务内部状态进行操控,缺乏灵活性。

第二类是通过Context.bindService()启动,通过Context.unbindService()结束,相对其特点在运用灵活,可以通过IBinder接口中获取Service的句柄,对Service状态进行检测。

从Android系统设计的架构上看,startService()是用于启动本地服务,bindService()更多是用于对远程服务进行绑定。当然,也可以结合两者进行混合式应用,先通过startService()启动服务,然后通过bindService()、unbindService()方法进行多次绑定,以获取Service服务在不同状态下的信息,最后通过stopService()方法结束Service运行。

2.2按照Service的寄存方式分为两种类型

本地服务(LocalService)寄存于当前的进程当中,当前进程结束后Service也会随之结束,Service可以随时与Activity等多个部件进行信息交换。Service服务不会自动启动线程,如果没有人工调用多线程方式进行启动,Service将寄存于主线程当中。

远程服务(RemoteService)独立寄存于另一进程中,通过AIDL(AndroidInterfaceDefinitionLanguage)接口定义语言,实现Android设备上的两个进程间通信(IPC)。AIDL的IPC机制是基于RPC(RemoteProceduceCall)远程过程调用协议建立的,用于约束两个进程间的通讯规则,供编译器生成代码。进程之间的通信信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。

在代码的实现上,remote服务和本地服务有相似的地方,而差异主要表现在AIDL的应用上,其次在启动方式上。

3、remore服务端代码

本实例通过AndroidAIDE编写代码,使用AIDE可以在android平台开发app,免去了Androidstudio的一系列配置,可惜的是AIDE目前集成androidapilevel21。

新建服务端工程,androidmanifest.xml中将入口类禁用,android:enabled="false",因为服务端不需要交互界面,它运行后台。注意服务类的注册信息service,android:process=":remote"表示服务将独立于客户端运行。注意过滤器的值hakka.jimbowhy.remote.ITimerService,客户端要想连接上服务端,绑定时的Intent必须指定一样的Action值,这样安卓系统才会匹配到服务并启动他。


<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="hakka.jimbowhy.remote">

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
        
        <activity
            android:enabled="false"
android:name=".MainTest"
android:label="@string/app_main">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
        
        <service
            android:label="@string/app_name"
            android:name=".RemoteService"
            android:process=":remote">
<intent-filter>
<actionandroid:name="hakka.jimbowhy.remote.ITimerService"/>
</intent-filter>
</service>
        
</application>

</manifest>

使用包名hakka.jimbowhy.remote,在代码目录新建aidl接口,IRemoteService.aidl,接口定义两个方法,sendNotify用来发送系统通告,getDemoService作为演示的服务功能,内容如下。

packagehakka.jimbowhy.remote;

interfaceIRemoteService
{
StringgetDemoService(intid);
voidsendNotify(Stringmsg);
}

有了这个接口定义以后,预编译一次,让aidl编译器生成中间文件,找到gen目录,可以发现IRemoteService.java中间文件,它定义了我们的接口IRemoteService并继承了android.os.IInterface。结果有个内部类Stub,他才是实现我们需要的remote服务类的存根。现在我们需要新建一个RemoteService.java来实现我们的服务类功能。注意,RemoteService好像本地服务那样实现,但是多了一个内部类ServiceStubextendsIRemoteService.Stub,它实现了aidl中间文件接口定义,这样服务端就和安卓系统的底层建立联系了。当客户端绑定服务时,我们的服务类就负责与客户端交互,而内部类ServiceStub就负责以安卓系统的底层交互。


/*
*Thisfileisauto-generated.DONOTMODIFY.
*Originalfile:/storage/emulated/0/Android/Lessons/Android_Demos/remote/app/src/main/java/hakka/jimbowhy/remote/IRemoteService.aidl
*/
packagehakka.jimbowhy.remote;
publicinterfaceIRemoteServiceextendsandroid.os.IInterface
{
    /**Local-sideIPCimplementationstubclass.*/
    publicstaticabstractclassStubextendsandroid.os.Binderimplementshakka.jimbowhy.remote.IRemoteService
    {
        privatestaticfinaljava.lang.StringDESCRIPTOR="hakka.jimbowhy.remote.IRemoteService";
        /**Constructthestubatattachittotheinterface.*/
        publicStub()
        {
            this.attachInterface(this,DESCRIPTOR);
        }
        /**
        *CastanIBinderobjectintoanhakka.jimbowhy.remote.IRemoteServiceinterface,
        *generatingaproxyifneeded.
        */
        publicstatichakka.jimbowhy.remote.IRemoteServiceasInterface(android.os.IBinderobj)
        {
            if((obj==null))
            {
                returnnull;
            }
            android.os.IInterfaceiin=obj.queryLocalInterface(DESCRIPTOR);
            if(((iin!=null)&&(iininstanceofhakka.jimbowhy.remote.IRemoteService)))
            {
                return((hakka.jimbowhy.remote.IRemoteService)iin);
            }
            returnnewhakka.jimbowhy.remote.IRemoteService.Stub.Proxy(obj);
        }
        @Overridepublicandroid.os.IBinderasBinder()
        {
            returnthis;
        }
        @OverridepublicbooleanonTransact(intcode,android.os.Parceldata,android.os.Parcelreply,intflags)throwsandroid.os.RemoteException
        {
            switch(code)
            {
                caseINTERFACE_TRANSACTION:
                    {
                        reply.writeString(DESCRIPTOR);
                        returntrue;
                    }
                caseTRANSACTION_getDemoService:
                    {
                        data.enforceInterface(DESCRIPTOR);
                        int_arg0;
                        _arg0=data.readInt();
                        java.lang.String_result=this.getDemoService(_arg0);
                        reply.writeNoException();
                        reply.writeString(_result);
                        returntrue;
                    }
                caseTRANSACTION_sendNotify:
                    {
                        data.enforceInterface(DESCRIPTOR);
                        java.lang.String_arg0;
                        _arg0=data.readString();
                        this.sendNotify(_arg0);
                        reply.writeNoException();
                        returntrue;
                    }
            }
            returnsuper.onTransact(code,data,reply,flags);
        }
        privatestaticclassProxyimplementshakka.jimbowhy.remote.IRemoteService
        {
            privateandroid.os.IBindermRemote;
            Proxy(android.os.IBinderremote)
            {
                mRemote=remote;
            }
            @Overridepublicandroid.os.IBinderasBinder()
            {
                returnmRemote;
            }
            publicjava.lang.StringgetInterfaceDescriptor()
            {
                returnDESCRIPTOR;
            }
            @Overridepublicjava.lang.StringgetDemoService(intid)throwsandroid.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);
                    _data.writeInt(id);
                    mRemote.transact(Stub.TRANSACTION_getDemoService,_data,_reply,0);
                    _reply.readException();
                    _result=_reply.readString();
                }
                finally
                {
                    _reply.recycle();
                    _data.recycle();
                }
                return_result;
            }
            @OverridepublicvoidsendNotify(java.lang.Stringmsg)throwsandroid.os.RemoteException
            {
                android.os.Parcel_data=android.os.Parcel.obtain();
                android.os.Parcel_reply=android.os.Parcel.obtain();
                try
                {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(msg);
                    mRemote.transact(Stub.TRANSACTION_sendNotify,_data,_reply,0);
                    _reply.readException();
                }
                finally
                {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        staticfinalintTRANSACTION_getDemoService=(android.os.IBinder.FIRST_CALL_TRANSACTION+0);
        staticfinalintTRANSACTION_sendNotify=(android.os.IBinder.FIRST_CALL_TRANSACTION+1);
    }
    publicjava.lang.StringgetDemoService(intid)throwsandroid.os.RemoteException;
    publicvoidsendNotify(java.lang.Stringmsg)throwsandroid.os.RemoteException;
}
package hakka.jimbowhy.remote;

import android.app.*;
import android.os.*;
import android.content.*;
import java.util.*;
import android.net.*;

public class RemoteService extends Service 
{
    private Random random = new Random();

    @Override
    public IBinder onBind(Intent i)
    {
        ServiceStub s = new ServiceStub();
        s.sendNotify("Remote Service onBind pid:"+
            android.os.Process.myPid()+i.getDataString());
        return s;
    }

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

    @Override
    public void onDestroy()
    {
        sendNotify("onDestory");
        super.onDestroy();
    }
    
    public void sendNotify(String msg)
    {
        int id = random.nextInt();

        Notification.Builder nb = new Notification.Builder(RemoteService.this);
        nb.setDefaults(Notification.DEFAULT_ALL);
        nb.setAutoCancel(true); // user fling to delete
        nb.setOngoing(false);
        nb.setCategory("ServiceNotify");

        Intent i = new Intent(
            Intent.ACTION_ANSWER, // register this action in activity
            Uri.parse("hakka://remote.jimbowhy.demo"));
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent pi = PendingIntent
            .getBroadcast(RemoteService.this, id, i, PendingIntent.FLAG_ONE_SHOT);
        nb.setContentIntent(pi); // click notification to trigger
        nb.setDeleteIntent(pi); // click clear Notification to trigger

        long[] patterm = {0,100,150,100,300,300};//[delay,vibrate,rest,vibrate,rest...]
        nb.setVibrate(patterm);

        nb.setContentTitle(getString(R.string.app_name));
        nb.setContentText(msg);
        nb.setSmallIcon(R.drawable.ic_launcher);

        Notification n = nb.getNotification();

        Object s = getSystemService(NOTIFICATION_SERVICE);
        NotificationManager nm = (NotificationManager)s;
        nm.notify(id, n);
    }
    
    public class ServiceStub extends IRemoteService.Stub
    {

        @Override
        public String getDemoService(int id) throws RemoteException
        {
            String ishukan = "日・月・火・水・木・金・土の七日を一区切りとした日時の単位。";
            String[] day = {
                "日曜日 にちようび",
                "月曜日 げつようび",
                "火曜日 かようひ",
                "水曜日 すいようひ",
                "木曜日 もくようび",
                "金曜日 きんようび",
                "土曜日 どようび"};
            if (id < 0 || id >= day.length) return ishukan;
            return day[id];
        }


        public void sendNotify(String msg)
        {
            RemoteService.this.sendNotify(msg);
        }
    }
}

sendNotify方法主要用来发送调试信息到通知栏,注意其中一行,ongoing=true表示一个持续的任务,系统就不会因为回收内存而将服务杀死,服务通过调用startForeground就会产生ongoing服务,即前台服务。前台服务不会被回收是个很大的特点,相应他的通告也是不可以在通知栏里移除的,除非服务停止了。这里不需要这种服务,设置false,这样用户就可以在通知栏将通告抹掉。

nb.setOngoing(false);

4、客户端代码

新建客户端工程,使用包名hakka.jimbowhy.client,设置启动入口类为.Client。以下为androidmanifest.xml文件内容,不用设置remote服务类:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="hakka.jimbowhy.client" >

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

</manifest>

在布局文件main.xml中,添加三个按钮,分别用来绑定、测试、取绑,设置好id如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/mainTextView"/>

    <LinearLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_gravity="bottom">

        <Button
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="Bind"
            android:id="@+id/mainButtonBind"/>

        <Button
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="Test"
            android:id="@+id/mainButtonTest"/>

        <Button
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="Unbind"
            android:id="@+id/mainButtonUnbind"/>

    </LinearLayout>

</LinearLayout>


接下来是java代码部分,在代码文件夹新建一个Client.java,注意包名hakka.jimbowhy.client,在写代码之前,先将aidl文件拷贝过来放到代码文件夹,目录结构保持不变,先预编译一次,aidl编译器会产生相应的java中间层文件,这样编写代码的时候就有提示功能了。


package hakka.jimbowhy.client;

import hakka.jimbowhy.remote.*;

import android.app.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.content.*;
import android.widget.*;
import java.util.*;
import android.net.*;

public class Client extends Activity implements 
View.OnClickListener, ServiceConnection
{
    public String ACTION = "hakka.jimbowhy.remote.ITimerService";
    private Boolean isBound = false;
    private IRemoteService service;
    private TextView box;

    @Override
    public void onServiceConnected(ComponentName cn, IBinder binder)
    {
        service = IRemoteService.Stub.asInterface(binder);
    }

    @Override
    public void onServiceDisconnected(ComponentName cn)
    {
        try{
            service.sendNotify("onServiceDisconnected");
        }catch(RemoteException e){
            box.setText(e.getMessage());
        }
    }


    @Override
    public void onClick(View v)
    {
        switch(v.getId())
        {
            case R.id.mainButtonBind:
                //Uri dat = Uri.parse("hakka://ipc");
                Intent intent = new Intent(ACTION);
                intent.setPackage("hakka.jimbowhy.remote");
                //intent.setData(dat);
                isBound = bindService(intent,this,
                                      BIND_WAIVE_PRIORITY|BIND_AUTO_CREATE);
                break;
            case R.id.mainButtonUnbind:
                if(isBound){
                    service = null;
                    unbindService(this);
                    isBound = false;
                }
                break;
            case R.id.mainButtonTest:
                try{
                    if( service!=null) {
                        box.setText(service.getDemoService(new Random().nextInt(8)));
                        service.sendNotify("MainTester pid:"+android.os.Process.myPid());
                    }
                }catch(RemoteException e){
                    box.setText(e.getMessage());
                }
                break;
        }
    }

    @Override
    protected void onDestroy()
    {
        service = null;
        super.onDestroy();
    }


    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        
        findViewById(R.id.mainButtonBind).setOnClickListener(this);
        findViewById(R.id.mainButtonTest).setOnClickListener(this);
        findViewById(R.id.mainButtonUnbind).setOnClickListener(this);
        box = (TextView)findViewById(R.id.mainTextView);
    }

}


5、运行测试

AndroidAIDL实现IPC服务与通告实战
AndroidAIDL实现IPC服务与通告实战
AndroidAIDL实现IPC服务与通告实战
AndroidAIDL实现IPC服务与通告实战
AndroidAIDL实现IPC服务与通告实战
AndroidAIDL实现IPC服务与通告实战

上图显示,客户端、服务端processid归属两个进程,因此是进程间调用,至此ipc服务交互实战完成。

参考:Android综合揭秘——全面剖释Service服务

上一篇 下一篇

猜你喜欢

热点阅读