《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL

1.2 Service与AIDL
Service是Android中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。但不要被“后台”二字所迷惑,Service默认并不会运行在子线程中,它也不运行在一个独立的进程中,它同样执行在UI线程中,因此,不要在Service中执行耗时的操作,除非你在Service中创建了子线程来完成耗时操作。

Service的运行不依赖于任何用户界面,即使程序被切换到后台或者用户打开了另外一个应用程序,Service仍然能够保持正常运行,这也正是Service的使用场景。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。

1.2.1 普通Service
Service的生命周期相对Activity来说简单得多,只有3个,分别为onCreate、onStartCommand和onDestory。一旦在项目的任何位置调用了Context 的startService()函数,相应的服务就会启动起来,首次创建时会调用onCreate函数,然后回调onStartCommand()函数。服务启动了之后会一直保持运行状态,直到stopService()或stopSelf()函数被调用。虽然每调用一次startService()函数,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()函数, 只需调用一个stopService()或stopSelf()函数,服务就会被停止。

通常的Service大致如下:

public class MyService extends Service {

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

    private void doMyJob(Intent intent){
        // 从Intent中获取数据
        // 执行相关操作
        new Thread(){
            @Override
            public void run() {
                // 耗时操作
            }
        }.start();
    }

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

与Activity一样,Service也需要在AndroidManifest.xml中进行注册,示例如下:

<service android:name=".service.MyService" />

上述示例表示注册一个在应用包service目录下的MyService服务,注册之后,当用户调用startService(new Intent(mContext,MyService.class)) 时会调用onStartCommand函数,我们在该函数中调用doMyJob,而在doMyJob中我们创建了一个线程来执行耗时操作,以避免阻塞UI线程。当我们的Service完成使命时,需要调用stopService来停止该服务。

1.2.2 IntentService
完成一个简单的后台任务需要这么麻烦,Android显然早就“洞察”了这一点。因此,提供了一个IntentService来完成这样的操作,IntentService将用户的请求执行在一个子线程中,用户只需要覆写onHandleIntent函数,并且在该函数中完成自己的耗时操作即可。需要注意的是,在任务执行完毕之后IntentService会调用stopSelf自我销毁,因此,它适用于完成一些短期的耗时任务。示例如下:

  public class MyIntentService extends IntentService {

    MyIntentService(){
        super(MyIntentService.class.getName());
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 这里执行耗时操作
    }
}

1.2.3 运行在前台的Service
Service默认是运行在后台的,因此,它的优先级相对比较低,当系统出现内存不足的情况时,它就有可能会被回收掉。如果希望Service可以一直保持运行状态,而不会由于系统内存不足被回收,可以将Service运行在前台。前台服务不仅不会被系统无情地回收,它还会在通知栏显示一条消息,下拉状态栏后可以看到更加详细的信息。例如,墨迹天气在前台运行了一个Service,并且在Service中定时更新通知栏上的天气信息,如图1-11所示。

下面我们就来实现一个类似于如图1-11所示的效果,首先我们定义一个服务,代码如下:

public class WeatherService extends Service {

    private static final int NOTIFY_ID = 123;

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

    /**
     * 在通知栏显示天气信息
     */
    private void showNotification() {
        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(this)
                        .setSmallIcon(R.drawable.weather)
                        .setContentTitle(getText(R.string.the_day))
                        .setContentText(getText(R.string.weather));
        // 创建通知被点击时触发的Intent
        Intent resultIntent = new Intent(this, MainActivity.class);

        // 创建任务栈Builder
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(resultIntent);
        PendingIntent resultPendingIntent =
                stackBuilder.getPendingIntent(
                        0, PendingIntent.FLAG_UPDATE_CURRENT);
        mBuilder.setContentIntent(resultPendingIntent);
        NotificationManager mNotifyMgr =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        // 构建通知
        final Notification notification = mBuilder.build() ;
        // 显示通知
        mNotifyMgr.notify(NOTIFY_ID, notification);
        // 启动为前台服务
        startForeground(NOTIFY_ID, notification);
    }
}

我们在onCreate函数中调用了showNotification函数显示通知,并且在最后调用startForeground将服务设置为前台服务。在AndroidManifest.xml注册之后我们就可以启动该Service了。效果如图1-12所示。

1.2.4 AIDL(Android接口描述语言)
AIDL(Android接口描述语言)是一种接口描述语言,通常用于进程间通信。编译器根据AIDL文件生成一个系列对应的Java类,通过预先定义的接口以及Binder机制达到进程间通信的目的。说白了,AIDL就是定义一个接口,客户端(调用端)通过bindService来与远程服务端建立一个连接,在该连接建立时会返回一个IBinder对象,该对象是服务端Binder的BinderProxy,在建立连接时,客户端通过asInterface函数将该BinderProxy对象包装成本地的Proxy,并将远程服务端的BinderProxy对象赋值给Proxy类的mRemote字段,就是通过mRemote执行远程函数调用。

在客户端新建一个AIDL文件,如图1-13所示。

在SsoAuth.aidl文件中会默认有一个basicTypes函数,我们在程序后面添加一个ssoAuth的函数用于SSO授权。代码如下:

interface SsoAuth {
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    /**
     * 实现SSO授权
     */
     void ssoAuth(String userName, String pwd);
}

因为客户端是调用端,因此,只需要定义AIDL文件,此时Rebuild一下工程就会生成一个SsoAuth.java类,该类根据SsoAuth.aidl文件生成,包含了我们在AIDL文件中定义的函数。因为AIDL通常用于进程间通信,因此,我们新建一个被调用端的工程,我们命名为aidl_server,然后将客户端的AIDL文件夹复制到aidl_server的app/src/main目录下,结构如图1-14所示。

此时相当于在客户端和被调用端都有同一份SsoAuth.aidl文件,它们的包名、类名完全一致,生成的SsoAuth.java类也完全一致,这样在远程调用时它们就能够拥有一致的类型。Rebuild被调用端工程之后就会生成SsoAuth.java文件,该文件中有一个Stub类实现了SsoAuth接口。我们首先需要定义一个Service子类,然后再定义一个继承自Stub的子类,并且在Service的onBind函数中返回这个Stub子类的对象。示例代码如下:

public class SinaSsoAuthService extends Service {

    SinaSsoImpl mBinder = new SinaSsoImpl();

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("","### sso auth created") ;
    }

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

    // 继承自Stub类,在这里实现ssoAuth函数
    class SinaSsoImpl extends SsoAuth.Stub {

        @Override
        public void ssoAuth(String userName, String pwd) throws RemoteException {
            Log.e("", "这里是新浪客户端, 执行SSO登录啦,用户名 : "
                                      + userName + ", 密码 : " + pwd) ;
        }

        @Override
        public void basicTypes(int anInt, long aLong,
            boolean aBoolean, float aFloat,
            double aDouble, String aString) throws RemoteException {
        }
    }
}

从上述代码中我们看到,实际上完成功能的是继承自Stub的SinaSsoImpl类,Service只提供了一个让SinaSsoImpl依附的外壳。完成SinaSsoAuthService之后我们需要将它注册在被调用端应用的Manifest中,注册代码如下:

<service
    android:name=".service.SinaSsoAuthService"
    android:exported="true"
    android:process=":remote"
    android:label="@string/app_name">
  <intent-filter>
          <action android:name="book.aidl_server.service.SinaSsoAuthService"/>
  </intent-filter>
</service>

然后先运行被调用端(也就是Server端)应用,并且在客户端中完成调用Server的代码。客户端Activity的代码如下:

public class MainActivity extends AppCompatActivity {

    SsoAuth mSsoAuth ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 执行操作
        findViewById(R.id.sso_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if ( mSsoAuth == null ) {
                    // 绑定远程服务,并且进行登录
                    bindSsoAuthService();
                } else {
                    doSsoAuth();
                }
            }
        });
    }

    private void bindSsoAuthService() {
        Intent intent = new Intent("book.aidl_server.service.SinaSsoAuthService") ;
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE) ;
    }

    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // 建立连接之后将Binder转换为mSsoAuth
            mSsoAuth = SsoAuth.Stub.asInterface(iBinder) ;
            doSsoAuth();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mSsoAuth = null;
        }
    } ;

    private void doSsoAuth() {
        try {
            // 执行登录,实际上调用的是Server端的ssoAuth函数
            mSsoAuth.ssoAuth("Mr.Simple", "pwd123");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

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

在上述Activity程序中,运行程序后点击登录按钮时会向Server端发起连接Service请求,在建立连接之后会将Binder对象转换为SsoAuth对象,然后调用SsoAuth对象的ssoAuth函数。此时的ssoAuth函数实际上调用的就是Server端中SinaSsoImpl类的实现。运行程序后点击登录按钮,如图1-15所示。

这一切的核心都是通过AIDL文件生成的Stub类以及其背后的Binder机制。首先我们看看生成的SsoAuth.java,Stub类就是该文件中的内部类。代码如下:

// 根据SsoAuth.aidl生成的接口
public interface SsoAuth extends android.os.IInterface{
    /** Stub类继承自Binder,并且实现了SsoAuth接口 */
    public static abstract class Stub extends android.os.Binder
        implements book.jtm_chap01.SsoAuth {
        private static final java.lang.String DESCRIPTOR = "book.jtm_chap01.SsoAuth";
        public Stub(){
            this.attachInterface(this, DESCRIPTOR);
        }
        /**
         * 将Binder转换为 book.jtm_chap01.SsoAuth接口或者包装为一个Proxy
         */
        public static book.jtm_chap01.SsoAuth asInterface(android.os.IBinder obj){
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof book.jtm_chap01.SsoAuth))) {
                return ((book.jtm_chap01.SsoAuth)iin);
            }
            return new book.jtm_chap01.SsoAuth.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);
                     // 代码省略
                    return true;
                 }
                case TRANSACTION_ssoAuth:    // 执行ssoAuth函数时提交给Binder的数据
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    this.ssoAuth(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        // 本地代理,通过Binder与服务端的对象进行交互
        private static class Proxy implements book.jtm_chap01.SsoAuth{
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote){
                mRemote = remote;
            }
            @Override public android.os.IBinder asBinder(){
                return mRemote;
            }
             // 代码省略
            /**
             * 实现SSO授权
             */
             @Override public void ssoAuth(java.lang.String userName,
                      java.lang.String pwd) 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.writeString(userName);
                    _data.writeString(pwd);
                    mRemote.transact(Stub.TRANSACTION_ssoAuth, _data, _reply, 0);
                    _reply.readException();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        static final int TRANSACTION_basicTypes
                     = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_ssoAuth
                     = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double
    aDouble, java.lang.String aString) throws android.os.RemoteException;
    // 实现SSO授权
    public void ssoAuth(java.lang.String userName, java.lang.String pwd)
               throws android.os.RemoteException;
}

在SsoAuth.java中自动生成了SsoAuth接口,该接口中有一个ssoAuth函数。但最,重要的是生成了一个Stub类,该类继承自Binder类,并且实现了SsoAuth接口。Stub里面最重要的就是asInterface()这个函数,在这个函数中会判断obj参数的类型,如果该obj是本地的接口类型,则认为不是进程间调用,此时将该obj转换成SsoAuth类型;否则会通过自动生成的另一个内部类Proxy来包装obj,将其赋值给Proxy中的mRemote字段。Proxy类也实现了SsoAuth接口,不同的是它是通过Binder机制来与远程进程进行交互,例如,在ssoAuth ()函数中,Proxy将通过Binder机制向服务端传递请求和数据,它请求的类型为TRANSACTION_ssoAuth,参数分别是String类型的userName和pwd。

对于服务端代码来说,它也有同一份SsoAuth.aidli以及SsoAuth.java,但不同的是服务端是指令的接收端,客户端的调用会通过Binder机制传递到服务端,最终调用Stub类中的onTransact函数。可以看到在case TRANSACTION_ssoAuth处执行了this.ssoAuth()函数,意思是当接收到客户端的TRANSACTION_ssoAuth请求时,执行this.ssoAuth()函数,通过客户端的分析我们知道,当我们调用ssoAuth()时实际上就是通过mRemote向服务端提交了一个TRANSACTION_ssoAuth请求,因此,这两端通过Binder机制就对接上了,我们可以简单地理解为C/S模式。

而在客户端调用bindService之后,如果绑定成功则会调用onServiceConnected(ComponentName name,IBinder service),这里的Service对象是BinderProxy类型,经过asInterface转换后被包装成了Proxy类型,但是调用的时候,执行的是服务端SinaSsoImpl中的ssoAuth()函数。因此, SinaSsoImpl实例mBinder被服务端包装成BinderProxy类型,再经过客户端的Proxy进行包装,通过Binder机制进行数据传输,实现进程间调用。

它们的调用时序图如图1-17所示。

打个比方说,有两个公司打算进行合作需要进行业务磋商,并且这次合作已经签署了合同,只剩下一些细节没有最终确定。但是由于大BOSS比较忙,因此各自都派了一个代表进行沟通。由于两家公司相距较远,双方代表都通过电话进行沟通。BOSS-A跟代表-A交代说,这次合作对方支付的酬劳不能低于十块钱,于是代表-A通过电话与代表B进行沟通,代表-B得到消息之后跑到BOSS-B的办公室请示,BOSS-B确认之后又由代表-B回复代表-A,代表-A最终反馈给BOSS-A。这个例子中的两个BOSS分别对应客户端和服务端,合同就对应了SsoAuth接口,而两个代表则对应了两端的Proxy,代表的通信方式则是电话,而代码的通信方式是Binder。

总体来说,使用AIDL并不是一件困难的事,但是理解AIDL的机制确实有一定的难度。也正是如此,Android通过AIDL这个机制将一些复杂的概念与逻辑通过自动生成类型的方式屏蔽掉,使得开发人员能够更简单地进行进程间通信。

时间: 2024-07-31 08:25:35

《Android开发进阶:从小工到专家》——第1章,第1.2节Service与AIDL的相关文章

《Android开发进阶:从小工到专家》——第1章,第1.1节Activity

第1章 Android的构成基石-四大组件 Android开发进阶:从小工到专家 由于本书的目标读者是有一定Android基础的开发人员,因此,本章不再介绍Android系统的架构.历史等知识,而是直接切入主题,从讲解Android的四大组件开始,然后一步一步深入学习开发中的重要知识点,使得我们能够从基本原理层面掌握Android开发基础知识. Android中最重要的是四大组件,即Activity.Service.ContentProvider和Broadcast.这4个组件分工明确,共同构成

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

第2章 创造出丰富多彩的UI-View与动画Android开发进阶:从小工到专家在第一章中,我们说到Android的用户界面构成,实际上就是Activity由一个搭载着视图树的Window构成.作为与用户直接交互的元素,UI控件变得尤为重要.本章将介绍部分常用且重要的控件.自定义控件.动画等内容,使我们进一步认识View,进入更丰富多彩的视图世界. 2.1 重要的View控件通常来说用户界面都是由Activity组成,Activity中关联了一个PhoneWindow创建,在这个窗口下则管理了一

《Android开发进阶:从小工到专家》——导读

目 录前 言 第1章 Android的构成基石-四大组件1.1节Activity1.2节Service与AIDL1.3节Broadcast(广播)1.4节ContentProvider(外共享数据)1.5节小结第2章 创造出丰富多彩的UI-View与动画2.1节重要的View控件2.2节必须掌握的最重要的技能--自定义控件2.3节Scroller的使用2.4节让应用更精彩--动画2.5节小结 第3章 保证App流畅的关键因素-多线程第4章 HTTP网络请求第5章 独特高效的数据存储-SQLite

Android开发进阶自定义控件之滑动开关实现方法【附demo源码下载】_Android

本文实例讲述了Android开发进阶自定义控件之滑动开关实现方法.分享给大家供大家参考,具体如下: 自定义开关控件 Android自定义控件一般有三种方式 1.继承Android固有的控件,在Android原生控件的基础上,进行添加功能和逻辑. 2.继承ViewGroup,这类自定义控件是可以往自己的布局里面添加其他的子控件的. 3.继承View,这类自定义控件没有跟原生的控件有太多的相似的地方,也不需要在自己的肚子里添加其他的子控件. ToggleView自定义开关控件表征上没有跟Androi

《Android开发进阶:从小工到专家》——第2章,第2.5节小结

2.5 小结本章学习了Android开发中最为重要的两个知识点,即自定义View与动画.通过自定义View,可以创造出丰富多彩的UI元素,但是由于篇幅有限,一些很重要的知识点并没有覆盖,例如Xfermode,在做一些特殊效果时灵活运用Xfermode将获得意想不到的效果.而对于Canvas以及Paint的细节也没有过多介绍.对于动画而言,我们了解了帧动画.补间动画.属性动画,而较新的VectorDrawable以及SVG等内容并没有覆盖,这些内容大家可以在学习完本章之后自行扩展.

Android开发进阶:如何读写Android文件

Android主要有四大主要组件组成:Activity.ContentProvider.Service.Intent组成.Android文件的运行主要需要读写四大组件的文件.本文将介绍如何读写Android文件,希望对正在进行Android开发的朋友有所帮助. 文件存放位置 在Android中文件的I/O是存放在/data/data/<package name>/file/filename目录下. 提示:Android是基于linux系统的,在linux的文件系统中不存在类似于Windows的

《Android开发进阶:从小工到专家》——第2章,第2.2节必须掌握的最重要的技能——自定义控件

2.2 必须掌握的最重要的技能--自定义控件 虽然Android已经自带了很多强大的UI控件,但是依旧不能满足所有开发人员的需求.通常开发人员需要实现设计师精心设计的视觉效果,这种情况下可能现有的控件就不能满足需求或者说使用现有的控件实现起来成本很高,此时我们只能寻找是否有类似的开源库,如果没有人实现过类似的效果,我们只能通过自定义View实现.因此,自定义View就成了开发人员必须掌握的最重要技能之一. 自定义View也有几种实现类型,分别为继承自View完全自定义.继承自现有控件(如Imag

《Android开发进阶:从小工到专家》——第2章,第2.4节让应用更精彩——动画

2.4 让应用更精彩--动画 为了使用户的交互更为流畅.自然,动画已经成为一款应用中不可缺少的部分.在Android中,动画的分类较多,有最早的帧动画.补间动画,从Android 3.0之后添加了属性动画,而在Android 5.0中又增加了VectorDrawable,使得Android的动画多种多样,能够满足用户的各种需求. 动画实际上就是在指定的时间段内持续地修改某个属性的值,使得该值在指定取值范围之内平滑的过渡.如图2-22所示是一个执行时长为40毫秒.将x从0平滑过渡为40的动画. 从

《Android开发进阶:从小工到专家》——第2章,第2.3节Scroller的使用

2.3 Scroller的使用 为了更好地理解下拉刷新的实现,我们先要了解Scroller的作用以及如何使用.这里我们将做一个简单的示例来说明. Scroller是一个帮助View滚动的辅助类,在使用它之前,用户需要通过startScroll来设置滚动的参数,即起始点坐标和(x,y)轴上要滚动的距离.Scroller它封装了滚动时间.要滚动的目标x轴和y轴,以及在每个时间内View应该滚动到的(x,y)轴的坐标点,这样用户就可以在有效的滚动周期内通过Scroller的getCurX()和getC