精华内容
下载资源
问答
  • 在不同应用程序之间交互数据(跨进程通讯),在Android SDK中提供了4种用于跨进程通讯的方式。 这4种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。其中Activity...
  • AIDL 是什么 AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)...
  • android进程间通信

    2015-03-01 16:01:02
    android使用aidl实现进程间通信,而且是双向通信
  • 本文介绍了Android进程间通信实践的示例代码,分享给大家,具体如下: 因为线程间的内存是共享的,所以它们之间的通信简单,比如可以通过共享变量等方式实现。而进程间想要通信就要麻烦许多了。要想实现进程间通信...
  • 一、概述 ...设计AIDL这门语言的目的就是为了实现进程间通信。在Android系统中,每个进程都运行在一块独立的内存中,在其中完成自己的各项活动,与其他进程都分隔开来。可是有时候我们又有应用间进
  • 【一图流】_02_一张图看懂 Android 进程间通信(IPC)Binder机制:  此图表述了Android系统_进程间通信(IPC)机制全部体系,其中重点放在 Android系统中 重用 的 Binder机制 上,详尽细致,希望对大家有用;
  • android 进程间通信demo

    2017-07-04 17:59:03
    一个简单的进程间通信
  • 通信又分为进程间通信和进程内通信,在这篇文章,我主要深入讲解Android系统所涉及到的所有进程间通信方式。 Android系统中有大量IPC(进程间通信)的场景,比如我们想要创建一个新的进程,需要通过Socket这种IPC...

    ​通信是Android开发必不可少的一部分,不管是我们做应用App开发,还是Android系统,都使用了大量的通信。通信又分为进程间通信和进程内通信,在这篇文章,我主要深入讲解Android系统所涉及到的所有进程间通信方式。

    Android系统中有大量IPC(进程间通信)的场景,比如我们想要创建一个新的进程,需要通过Socket这种IPC方式去让Zygote Fork新进程;如果我们要杀掉一个进程,需要通过信号这种IPC方式去将SIGNAL_KILL信号传递到系统内核;如果我们想要唤醒主线程处于休眠中的Looper,需要管道这种IPC方式来唤醒;我们想要在应用开发中使用AIDL,广播或者Messager等方式来进行跨进程通信,其实底层都是使用了Binder这种IPC方式。

    那么,Android到底有多少种进程间通信的方式呢?什么样的场景要选择什么样的通信方式呢?这些IPC通信方式怎么使用呢?这些IPC通信的底层原理又是什么呢?看完这篇文章,你就能回答这几个问题了。

    我们先通过下面一览Android系统所具有的IPC通信方式
    在这里插入图片描述

    可以看到,Android所拥有的IPC总共有这些:

    • 基于Unix系统的IPC的管道,FIFO,信号
    • 基于SystemV和Posix系统的IPC的消息队列,信号量,共享内存
    • 基于Socket的IPC
    • Linux的内存映射函数mmap()
    • Linux 2.6.22版本后才有的eventfd
    • Android系统独有的Binder和匿名共享内存Ashmen

    下面,我会详细介绍这些IPC的通信机制。

    管道

    PIPE和FIFO的使用及原理

    PIPE和FIFO都是指管道,只是PIPE独指匿名管道,FIFO独指有名管道,我们先看一下管道的数据结构以及他们的使用方式:

    //匿名管道(PIPE)
    #include <unistd.h>
    int pipe (int fd[2]); //创建pipe
    ssize_t write(int fd, const void *buf, size_t count);   //写数据
    ssize_t read(int fd, void *buf, size_t count);     //读数据//有名管道(FIFO)
    #include<sys/stat.h>        
    #include <unistd.h>
    int mkfifo(const char *path, mode_t mode);  //创建fifo文件
    int open(const char *pathname, int flags);  //打开fifo文件
    ssize_t write(int fd, const void *buf, size_t count);   //写数据
    ssize_t read(int fd, void *buf, size_t count);     //读数据
    

    可以看到,匿名管道通过pipe函数创建,pipe函数通过在内核的虚拟文件系统中创建两个pipefs虚拟文件(不清楚虚拟文件的可以去了解下Linux的虚拟文件系统VFS),并返回这两个虚拟文件描述符,有了这两个文件描述,我们就能进行跨进程通信了。
    在这里插入图片描述

    匿名管道是单向半双工的通信方式,单向即意味着只能一端读另一端写,半双工意味着不能同时读和写,其中文件描述符fd[1]只能用来写,文件描述符f[0]只能用来读,pipe创建好后,我们就可以用Linux标准的文件读取函数read和写入函数write来对pipe进行读写了。

    为什么pipe是匿名管道呢?因为pipefs文件是特殊的虚拟文件系统,并不会显示在VFS的目录中,所以用户不可见,既然用户不可见,那么又怎么能进行进程间通信呢?因为pipe是匿名的,所以它只支持父子和兄弟进程之间的通信。通过fork创建父子进程或者通过clone创建兄弟进程的时候,会共享内存拷贝,这时,子进程或兄弟进程就能在共享拷贝中拿到pipe的文件描述符进行通信了。我们通过下图看一下通信的流程。
    在这里插入图片描述
    接着说有名管道FIFO,FIFO是半双工双向通信,所以通过FIFO创建的管道既能读也能写,但是不能同时读和写,FIFO本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,这样的数据结构能保证信息交流的顺序。

    FIFO使用也很简单,通过mkfifo函数创建管道,它同样会在内核的虚拟文件系统中创建一个FIFO虚拟文件,FIFO文件在在VFS目录中可见,所以他是有名管道,FIFO创建后,我们需要调用open函数打开这个fifo文件,然后才能通过write和read函数进行读写

    我们来总结一下pipe和fifo的异同点

    相同点

    • IPC的本质都是通过在内核创建虚拟文件,并且调用文件读写函数来进行数据通信
    • 都只能接收字节流数据
    • 都是半双工通信

    不同点

    • pipe是单向通信,fifo可以双向通信
    • pipe只能在父子,兄弟进程间通信,fifo没有这个限制

    那么管道的使用场景是什么呢?匿名管道只能用在亲属进程之间通信,而且传输的数量小,一般只支持4K,不适合大数据的交换数据和不同进程间的通信,但是使用简单方便,因为是单向通信,所以不存在并发问题。虽然FIFO能在任意两个进程间进行通信,但是因为FIFO是可以双向通信的,这样也不可避免的带来了并发的问题,我们需要花费比较大的精力用来控制并发问题。

    管道在Android系统中的使用场景

    下面说一下Android系统中具体使用到管道的场景:Looper。Looper不就是一个消息队列吗?怎么还使用到了管道呢?其实在Android 6.0 以下版本中,主线程Looper的唤醒就使用到了管道。

    //文件-->/system/core/libutils/Looper.cpp
    Looper::Looper(bool allowNonCallbacks) :
            mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
            mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
        int wakeFds[2];
        int result = pipe(wakeFds);  //创建pipe
    ​
        mWakeReadPipeFd = wakeFds[0];
        mWakeWritePipeFd = wakeFds[1];
    ​
        result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
        LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
                errno);
    ​
        result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
        LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
                errno);
    ​
        mIdling = false;// Allocate the epoll instance and register the wake pipe.
        mEpollFd = epoll_create(EPOLL_SIZE_HINT);
        LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);struct epoll_event eventItem;
        memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = EPOLLIN;
        eventItem.data.fd = mWakeReadPipeFd;
        result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
        LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
                errno);
    }
    

    从第上面代码可以看到,native层的Looper的构造函数就使用了pipe来创建管道,通过mWakeReadPipeFd,mWakeWritePipeFd这两个文件描述符的命名也看出,它是用来做唤醒的,我们就来看一下具体的唤醒的实现吧。

    //文件-->/system/core/libutils/Looper.cpp
    void Looper::wake() {
        ssize_t nWrite;
        do {
            nWrite = write(mWakeWritePipeFd, "W", 1);
        } while (nWrite == -1 && errno == EINTR);
        if (nWrite != 1) {
            if (errno != EAGAIN) {
                ALOGW("Could not write wake signal, errno=%d", errno);
            }
        }
    }
    

    可以看到,唤醒函数其实就是往管道mWakeWritePipeFd里写入一个字母“W”,mWakeReadPipeFd接收到数据后,就会唤醒Looper。

    信号

    信号的使用及原理

    信号实质上是一种软中断,既然是一种中断,就说明信号是异步的,信号接收函数不需要一直阻塞等待信号的到达。当信号发出后,如果有地方注册了这个信号,就会执行响应函数,如果没有地方注册这个信号,该信号就会被忽略。我们来看一下信号的使用方法。

    #include <signal.h>
    sighandler_t signal(int signum, sighandler_t handler);  //信号注册函数
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //信号注册函数
    struct sigaction {
       void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
       void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
       sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
       int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
     };int kill(pid_t pid, int sig);  //信号发送函数
    int sigqueue(pid_t pid, int sig, const union sigval value);   //信号发送函数
    //……
    

    注册信号有两个方法

    • **signal()**函数:signal不支持传递信息,signum入参为信号量,handler入参为信号处理函数
    • **sigaction()**函数:sigaction支持传递信息,信息放在sigaction数据结构中

    信号发送函数比较多,这里我列举一下。

    • kill():用于向进程或进程组发送信号;
    • sigqueue():只能向一个进程发送信号,不能向进程组发送信号;
    • alarm():用于调用进程指定时间后发出SIGALARM信号;
    • setitimer():设置定时器,计时达到后给进程发送SIGALRM信号,功能比alarm更强大;
    • abort():向进程发送SIGABORT信号,默认进程会异常退出。
    • raise():用于向进程自身发送信号;

    通过kill -l指令可以查看Android手机支持的信号,从下图可以看到,总共有64个,前31个信号是普通信号,后33个信号是实时信号,实时信号支持队列,可以保证信号不会丢失。
    在这里插入图片描述
    我列举一下前几个信号的作用,其他的就不讲解了

    1SIGHUP挂起
    2SIGINT中断
    3SIGQUIT中断
    3SIGQUIT退出
    4SIGILL非法指令
    5SIGTRAP断点或陷阱指令
    6SIGABRTabort发出的信号
    7SIGBUS非法内存访问
    8SIGFPE浮点异常
    9SIGKILL杀进程信息

    当我们调用信号发送函数后,信号是怎么传到注册的方法调用中去的呢?这里以kill()这个信号发送函数讲解一下这个流程。

    kill()函数会经过系统调用方法sys_tkill()进入内核,sys_tkill是SYSCALL_DEFINE2这个方法来实现,这个方法的实现是一个宏定义。我会从这个方法一路往底追踪,这里我会忽略细节实现,只看关键部分的代码。

    //文件-->syscalls.h
    asmlinkage long sys_kill(int pid, int sig);//文件-->kernel/signal.c
    SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
    {
      struct kernel_siginfo info;clear_siginfo(&info);
      info.si_signo = sig;
      info.si_errno = 0;
      info.si_code = SI_USER;
      info.si_pid = task_tgid_vnr(current);
      info.si_uid = from_kuid_munged(current_user_ns(), current_uid());return kill_something_info(sig, &info, pid);
    }static int kill_something_info(int sig, struct kernel_siginfo *info, pid_t pid)
    {
      int ret;if (pid > 0) {  
        ret = kill_pid_info(sig, info, find_vpid(pid));
        return ret;
      }
      ……
    }int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid)
    {
      ……
      for (;;) {
            error = group_send_sig_info(sig, info, p, PIDTYPE_TGID);
        ……
      }
    }int group_send_sig_info(int sig, struct kernel_siginfo *info,
          struct task_struct *p, enum pid_type type)
    {
      ……
       ret = do_send_sig_info(sig, info, p, type);
      return ret;
    }int do_send_sig_info(int sig, struct kernel_siginfo *info, struct task_struct *p,
          enum pid_type type)
    {
      ……
      ret = send_signal(sig, info, p, type);
      return ret;
    }static int send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
          enum pid_type type)
    {
      return __send_signal(sig, info, t, type, from_ancestor_ns);
    }
    

    我们从sys_kill函数一路追踪,最终调用了__send_signal函数,我们接着看这个函数的实现。

    //文件-->kernel/signal.c
    static int __send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
          enum pid_type type, int from_ancestor_ns)
    {
      ……
    out_set:
      signalfd_notify(t, sig);  //将信号发送给监听的fd
      sigaddset(&pending->signal, sig);
      complete_signal(sig, t, type);  //完成信号发送
    ret:
      trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
      return ret;
    }static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
    {
      struct signal_struct *signal = p->signal;
      struct task_struct *t;//寻找处理信号的线程
      if (wants_signal(sig, p))
        t = p;
      else if ((type == PIDTYPE_PID) || thread_group_empty(p))
        return;
      else {
        t = signal->curr_target;
        while (!wants_signal(sig, t)) {
          t = next_thread(t);
          if (t == signal->curr_target)
            return;
        }
        signal->curr_target = t;
      }//如果是SIGKILL信号,则杀掉线程组
      if (sig_fatal(p, sig) &&
          !(signal->flags & SIGNAL_GROUP_EXIT) &&
          !sigismember(&t->real_blocked, sig) &&
          (sig == SIGKILL || !p->ptrace)) {
        /*
         * This signal will be fatal to the whole group.
         */
        if (!sig_kernel_coredump(sig)) {
          /*
           * Start a group exit and wake everybody up.
           * This way we don't have other threads
           * running and doing things after a slower
           * thread has the fatal signal pending.
           */
          signal->flags = SIGNAL_GROUP_EXIT;
          signal->group_exit_code = sig;
          signal->group_stop_count = 0;
          t = p;
          do {
            task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
            sigaddset(&t->pending.signal, SIGKILL);
            signal_wake_up(t, 1);
          } while_each_thread(p, t);
          return;
        }
      }
      /*
       * The signal is already in the shared-pending queue.
       * Tell the chosen thread to wake up and dequeue it.
       */
      signal_wake_up(t, sig == SIGKILL);
      return;
    }
    

    可以看到,信号最终被分发到了监听的fd中,交给了我们注册的函数处理,从最后部分也可以看到,如果是SIGKILL信号,内核会专门处理去杀进程。这里我详细讲了发送信号的底层实现,关于注册信号的底层实现,就不再这里详细讲了,有兴趣的可以自己去研究。

    信号在Android中的使用场景

    我们已经知道如何使用信号以及它的原理,那么我们在来看一个Android系统中使用信号的场景:杀进程。从上面部分可以看到,SIGKILL信号是由内核捕获并处理的,我们看一下Android是怎么调用杀进程的信号的吧。

    //文件-->Process.java
    public static final void killProcess(int pid) {
        sendSignal(pid, SIGNAL_KILL); 
    }//文件-->android_util_Process.cpp
    void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig) {
        if (pid > 0) {
            //打印Signal信息
            ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
            kill(pid, sig);
        }
    }
    

    可以看到,当我们调用Process的killProcess函数杀掉某个进程时,最终会调用到native方法kill(),入参sig信号量就是SIGKILL,这个kill()方法,就是我在上面讲的信号量发送函数,最终内核会响应我们的SIGKILL,杀掉进程。

    我们已经了解了基于Unix的三种通信方式以及他们在Android系统上的应用,我们接着来看看另外三种IPC通信方式,消息队列,信号量和共享内存。这三种IPC方式有基于SystemV和基于Posix的两个版本,由于有些Linux系统并没有实现基于POSIX的IPC,所以这儿就只说SystemV的IPC了,他们在本质上其实都是相似的。

    消息队列

    消息队列的使用和原理

    我们首先看看消息队列的创建及其如何使用

    #include <sys/ipc.h>
    #include <sys/msg.h>int msgget(key_t, key, int msgflg); //创建和访问消息队列
    int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);  //发送消息
    int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg); //获取消息
    

    我们可以通过**msgget()**函数来创建消息队列,它会在内核空间创建一个消息链表,**msgsend()**函数往消息队列发送消息,msgrcv()函数获取消息队列里的数据。

    在这里插入图片描述

    通过消息发送和接收函数可以看到,消息队列的每个消息消息都有msgid,msgtype,msgflg字段,msgid是消息的队列标识符,msgtype是消息的类型,发送函数中放在msg_ptr这个结构体里,msgflg用来控制读取消息时队列已满或者队列为空时的操作。

    我在这里介绍一下消息队列的数据结构,它是一个消息的链表,存放在内核中并由消息队列标识符标识,也就是上面提到的msgid,标识符标识用大于0的整数表示,并且每中标识符的消息队列都有自己的链表。它的表现结构如下图:

    在这里插入图片描述

    消息队列有哪些优点呢?它克服了Linux早期IPC机制的很多缺点,比如消息队列具有异步能力,又克服了具有同样能力的信号承载信息量少的问题;具有数据传输能力,又克服了管道只能承载无格式字节流以及缓冲区大小受限的问题。但是缺点是消息队列比信号和管道都要更加重量,在内核中会使用更多内存,并且消息队列能传输的数据也有限制,一般上限都是16kb。

    消息队列在Android中的使用场景

    受限于性能,数据量等问题的限制,Android系统没有直接使用Linux消息队列来进行IPC的场景,但是有大量的场景都利用了消息队列的特性来设计通信方案,比如我们最频繁使用的Handler,就是一个消息队列,由于Handler只是进程内的通信方式,所以它的实现不在这儿讨论,消息队列的架构模型被非常多的场景使用,主要有下面几个有点原因。

    • 解耦:消息队列可以实现两个模块之间的解耦,两个需要通信的模块不需要之间对接,发送方只需要将消息丢到队列,接收方只需要从队列里面取消息。
    • 异步:我们可以将多条消息并行的发送给消息队列,然后不同的模块去并行的处理,在这种方案下,我们不需要串行的处理任务。
    • 缓冲:消息队列的数据结构就是一个缓冲池,可以帮助我们减轻流量过大的压力。

    关于消息队列的介绍就讲到这儿了,它并不是一个常用的Linux IPC通信方式,我们接着信号量。

    信号量

    信号量的使用和原理

    信号量和信号是不同的IPC通信机制,信号量是在进程之间传递是一个整数值,信号量只有三种操作可以进行:初始化,P操作,V操作,我们看一下具体的使用函数和数据结构。

    #include<sys/sem.h>int semget(key_t key, int num_sems, int sem_flags);//创建新信号量或获取已有的信号量
    int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);//改变信号量的值struct sembuf{
        short sem_num;
        short sem_op;//通常是两个数,一个是-1,即P操作,一个是+1,即V操作。
        short sem_flg;//跟踪信号
    };
    

    我们通过semget()函数获取或者创建一个信号量,并返回一个信号量id,有了这个id,我们就可以通过semop()函数进行V和P的操作。V就是将这个信号量加1,P就是将信号量减1。这三种操作都是原子操作,我们通常用信号量来进行并发和同步的控制。

    讲到了信号量,不免要提一下互斥锁Mutex,信号量可以是非负整数,互斥锁只能是0和1两个值,我们可以将Mutex理解为特殊的信号量。在大部分情况下,用互斥锁来做并发的控制会比信号量更方便。关于Android或者Java的线程并发,我会专门写一篇文章来讲,也就不在这儿再继续深入讲了。

    共享内存

    共享内存的使用和原理

    还是先看共享内存的使用方法,我主要介绍两个函数:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg); //申请共享内存
    void *shmat(int shmid, const void *shmaddr, int shmflg); //把共享内存映射到进程的地址空间
    

    通过**shmget()**函数申请共享内存,它的入参如下

    • key:用来唯一确定这片内存的标识,。
    • size:就是我们申请内存的大小
    • shmflg:读写权限
    • 返回值:这个操作会返回一个id, 我们一般称为 shmid

    通过**shmat()**函数将我们申请到的共享内存映射到自己的用户空间,映射成功会返回地址,有了这个地址,我们就可以随意的读写数据了,我们继续看一下这个函数的入参

    • shmid :我们申请内存时, 返回的shmid
    • shmaddr:共享内存在进程的内存地址,传NULL让内核自己决定一个合适的地址位置.
    • shmflg:读写权限
    • 返回值:映射后进程内的地址指针, 代表内存的头部地址

    共享内存的原理是在内存中单独开辟的一段内存空间,这段内存空间其实就是一个tempfs(临时虚拟文件),tempfs是VFS的一种文件系统,挂载在/dev/shm上,前面提到的管道pipefs也是VFS的一种文件系统。
    在这里插入图片描述

    由于共享的内存空间对使用和接收进程来讲,完全无感知,就像是在自己的内存上读写数据一样,所以也是效率最高的一种IPC方式,上面提到的IPC的方式都是在内核空间中开辟内存来存储数据,写数据时,需要将数据从用户空间拷贝到内核空间,读数据时,需要从内核空间拷贝到自己的用户空间,而共享内存就只需要一次拷贝,而且共享内存不是在内核开辟空间,所以可以传输的数据量大。

    但是共享内存最大的缺点就是没有并发的控制,我们一般通过信号量配合共享内存使用,进行同步和并发的控制。

    Android中共享内存的使用场景

    共享内存在Android系统中主要的使用场景是用来传输大数据,并且Android并没有直接使用Linux原生的共享内存方式,而是设计了Ashmem匿名共享内存。之前说到有名管道和匿名管道的区别在于有名管道可以在vfs目录树中查看到这个管道的文件,但是匿名管道不行,所以匿名共享内存同样也是无法在vfs目录中查看到的,Android之所以要设计匿名共享内存,我觉得主要是为了安全性的考虑吧。

    我们来看看共享内存的一个使用场景,在Android中,如果我们想要将当前的界面显示出来,需要将当前界面的图元数据传递Surfaceflinger去做图层混合,图层混合之后的数据会直接送入帧缓存,送入帧缓存后,显卡就会直接取出帧缓存里的图元数据显示了。那么我们如何将应用的Activity的图元数据传递给SurfaceFlinger呢?想要将图像数据这样比较大的数据跨进程传输,靠binder是不行的,所以这儿便用到匿名共享内存。

    在这里插入图片描述

    从谷歌官方提供的架构图可以看到,图元数据是通过BufferQueue传递到SurfaceFlinger去的,当我们想要绘制图像的时候,需要从BufferQueue中申请一个Buffer,Buffer会调用Gralloc模块来分配共享内存当作图元缓冲区存放我们的图元数据。我们看一下代码的实现。

    //文件-->hardware/libhardware/modules/gralloc/gralloc.cpp
    static int gralloc_alloc_buffer(alloc_device_t* dev,
            size_t size, int usage, buffer_handle_t* pHandle)
    {
        int err = 0;
        int fd = -1;
        size = roundUpToPageSize(size);
        // 创建共享内存,并且设定名字跟size
        fd = ashmem_create_region("gralloc-buffer", size);
        if (err == 0) {
            private_handle_t* hnd = new private_handle_t(fd, size, 0);
            gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
                    dev->common.module);
             // 执行mmap,将内存映射到自己的进程
            err = mapBuffer(module, hnd);
            if (err == 0) {
                *pHandle = hnd;
            }
        }return err;
    }int mapBuffer(gralloc_module_t const* module,
                private_handle_t* hnd)
    {
            void* vaddr; 
            return gralloc_map(module, hnd, &vaddr);
        }static int gralloc_map(gralloc_module_t const* module,
            buffer_handle_t handle,
            void** vaddr)
    {
        private_handle_t* hnd = (private_handle_t*)handle;
        if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
            size_t size = hnd->size;
            //映射创建的匿名共享内存
            void* mappedAddress = mmap(0, size,
                    PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
            if (mappedAddress == MAP_FAILED) {
                return -errno;
            }
            hnd->base = intptr_t(mappedAddress) + hnd->offset;
        }
        *vaddr = (void*)hnd->base;
        return 0;
    }
    

    可以看到Android的匿名共享内存是通过ashmem_create_region() 函数来申请共享内存的,它会在/dev/ashmem下创建一个虚拟文件,Linux原生共享内存是通过shmget()函数,并会在/dev/shm下创建虚拟文件。

    匿名共享内存是通过**mmap()**函数将申请到的内存映射到自己的进程空间,而Linux是通过*shmat()函数。虽然函数不一样,但是Android的匿名共享内存和Linux的共享内存在本质上是大同小异的。

    Socket

    Socket的使用和原理

    socket套接字本来是设计给基于TCP/IP协议的网络通信使用的,但由于它是一种C/S架构模型,即客户端服务器端架构,这种模型能带来很大的安全性以及快速的响应能力,所以也常常用在进程之间的通信上。Socket的使用方式比上面前面提到的其他IPC都要复杂很多,我们先通过下图了解它的使用流程。
    在这里插入图片描述

    我们在看看具体的函数

    #include <sys/socket.h>
    #include <unistd.h>
    #include <unistd.h>
    int socket(int protofamily, int type, int protocol);//创建socket
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定socket
    int listen(int sockfd, int backlog);//监听端口号
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//客户端请求建立连接
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//服务端接收连接请求
    ssize_t send(int sockfd, const void *buf, size_t len, int flags); //IO写函数
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);//IO读函数
    int close(int fd); //关闭函数
    

    Linux系统中万物皆文件,所以Socket也是一个虚拟文件,socket文件的数据结构中包含了当前主机的ip地址,当前主机进程的端口号,发送端主机的ip地址等信息,通过这些信息,我们可以在虚拟文件系统中唯一定位到一个Socket文件,通过对这个文件的读写达到通信的目的。

    Socket在Android系统中的使用场景

    当我们使用socket来进行进程间的通信时,实际是通过将IP设置为127.0.0.1这个本地IP来实现的,Android系统为我们提供了LocalSocket来进行进程间的通信,LocalSocket的实质也是对Socket的封装,通过直接使用LocalSocket,我们省掉了设置本机IP等一系列繁琐的操作。

    我们看一个LocalSocket的使用场景:当我们启动一个App应用时,如果当前的应用的进程不存在,AMS会通过Socket通知Zygote去Fork新进程。它的代码实现如下。

    //文件-->frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
    // 服务端
    public static void main(String argv[]) {
        ZygoteServer zygoteServer = new ZygoteServer();
        ……
        zygoteServer.registerServerSocket(socketName);
        ……
        Log.i(TAG, "Accepting command socket connections");
        zygoteServer.runSelectLoop(abiList);
        zygoteServer.closeServerSocket();
        ……
    }
    

    ZygoteInit启动时,会创建一个ZygoteServer,然后fork生成System Server进程,接着启动整个Framwork的Server,最终执行zygoteServer的runSelectLoop函数,始终等待其他进程发送过来的fork进程的消息。在上面的代码中,我只展示了ZygoteServer相关的代码,启动System Server等其他流程的代码我全部省略了,有兴趣的可以去看看这一块的源码。我们接着看registerServerSocket函数和runSelectLoop函数的实现

    //文件-->/frameworks/base/core/java/com/android/internal/os/ZygoteServer.java
    void registerServerSocket(String socketName) {
        if (mServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }
            try {
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
                mServerSocket = new LocalServerSocket(fd);
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
    ​
        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    boolean done = peers.get(i).runOnce(this);
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }
    

    可以看到registerServerSocket函数实际是创建了LocalServerSocket,这个LocalServerSocket的名字就叫“zygote”,runSelectLoop函数将ServerSocket加入多路复用模型里,当收到消息时便调用runOnce方法去fork进程。

    我们已经了解了Server端了,我们接着看一下Client端

    //文件-->/frameworks/base/core/java/android/os/Process.java 
    //客户端  
    public static final ProcessStartResult start(final String processClass,
                                      final String niceName,
                                      int uid, int gid, int[] gids,
                                      int debugFlags, int mountExternal,
                                      int targetSdkVersion,
                                      String seInfo,
                                      String abi,
                                      String instructionSet,
                                      String appDataDir,
                                      String invokeWith,
                                      String[] zygoteArgs) {
            return zygoteProcess.start(processClass, niceName, uid, gid, gids,
                        debugFlags, mountExternal, targetSdkVersion, seInfo,
                        abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
        }//文件-->/frameworks/base/core/java/android/os/ZygoteProcess.jav
    public final Process.ProcessStartResult start(final String processClass,
                                              final String niceName,
                                              int uid, int gid, int[] gids,
                                              int debugFlags, int mountExternal,
                                              int targetSdkVersion,
                                              String seInfo,
                                              String abi,
                                              String instructionSet,
                                              String appDataDir,
                                              String invokeWith,
                                              String[] zygoteArgs) {
      try {
          return startViaZygote(processClass, niceName, uid, gid, gids,
                  debugFlags, mountExternal, targetSdkVersion, seInfo,
                  abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
      } catch (ZygoteStartFailedEx ex) {
          Log.e(LOG_TAG,
                  "Starting VM process through Zygote failed");
          throw new RuntimeException(
                  "Starting VM process through Zygote failed", ex);
      }
    }private Process.ProcessStartResult startViaZygote(final String processClass,
                                                  final String niceName,
                                                  final int uid, final int gid,
                                                  final int[] gids,
                                                  int debugFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  String seInfo,
                                                  String abi,
                                                  String instructionSet,
                                                  String appDataDir,
                                                  String invokeWith,
                                                  String[] extraArgs)
                                                  throws ZygoteStartFailedEx {
    ……
    synchronized(mLock) {
        //连接服务端socket,并发送数据
        return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
    }
    }private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
      if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
          try {
              primaryZygoteState = ZygoteState.connect(mSocket);
          } catch (IOException ioe) {
              throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
          }
      }
      ……
    }public static ZygoteState connect(String socketAddress) throws IOException {
        DataInputStream zygoteInputStream = null;
        BufferedWriter zygoteWriter = null;
        final LocalSocket zygoteSocket = new LocalSocket();
        zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                LocalSocketAddress.Namespace.RESERVED));
    ​
        zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
    ​
        zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                zygoteSocket.getOutputStream()), 256);return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                Arrays.asList(abiListString.split(",")));
    }private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
        ZygoteState zygoteState, ArrayList<String> args)
        throws ZygoteStartFailedEx {
          int sz = args.size();
          for (int i = 0; i < sz; i++) {
              if (args.get(i).indexOf('\n') >= 0) {
                  throw new ZygoteStartFailedEx("embedded newlines not allowed");
              }
          }
          final BufferedWriter writer = zygoteState.writer;
          final DataInputStream inputStream = zygoteState.inputStream;
    ​
          writer.write(Integer.toString(args.size()));
          writer.newLine();for (int i = 0; i < sz; i++) {
              String arg = args.get(i);
              writer.write(arg);
              writer.newLine();
          }
    ​
          writer.flush();
          Process.ProcessStartResult result = new Process.ProcessStartResult();
          result.pid = inputStream.readInt();
          result.usingWrapper = inputStream.readBoolean();if (result.pid < 0) {
              throw new ZygoteStartFailedEx("fork() failed");
          }
          return result;
    }
    

    从上面的代码实现可以看到,当AMS调用Process的start()函数时,最终执行到了ZygoteProcess类中的openZygoteSocketIfNeeded() 函数,连接socket,然后调用zygoteSendArgsAndGetResult() 函数通过LocalSocket 往LocalServerSocket发送消息 。

    为什么Android fork进程要用Socket,而不用Binder呢?这个问题留给大家去思考。

    mmap函数

    mmap是一个很重要的函数,它可以实现共享内存,但并不像SystemV和Posix的共享内存存粹的只用于共享内存,mmap()的设计,主要是用来做文件的映射的,它提供了我们一种新的访问文件的方案。
    在这里插入图片描述

    mmap函数的使用非常简单,我们来看一下

    #include <sys/mman.h>
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    
    • addr:用于指定映射到进程空间的起始地址,为了应用程序的可移植性,一般设置为NULL,让内核来选择一个合适的地址
    • length:表示映射到进程地址空间的大小
    • prot:用于设置内核映射区域的读写属性等。
    • flags:用于设置内存映射的属性,例如共享映射、私有映射等。
    • fd:表示这个是一个文件映射,fd是打开文件的句柄。
    • offset:在文件映射时,表示文件的偏移量。

    常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制,这种机制会造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

    而使用mmap操作文件中,由于不需要经过内核空间的数据缓存,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

    mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程,因此mmap效率很高。

    Android中mmap()的使用场景

    mmap()使用非常频繁,看过Android系统源码的人,肯定看到过大量的地方使用mmap()函数,比如上面提到的匿名共享内存的使用就使用到了mmap来映射/dev/ashmem里的文件。

    这里我再介绍一种mmap()在Android系统上的使用场景,mmap的设计目的就是为了让文件的访问更有效率,所以当APK进行安装时,为了更高效的读取APK包里面的文件,同样也用到了mmap函数。Dalvik在安装应用时,需要加载dex文件,然后进行odex优化处理,优化函数为dvmContinueOptimization,我们看一下他的大致实现。

    bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
        const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
    {
          ……
          //通过mmap映射dex文件
          mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
                      MAP_SHARED, fd, 0);
          if (mapAddr == MAP_FAILED) {
              ALOGE("unable to mmap DEX cache: %s", strerror(errno));
              goto bail;
          }
    ​
          ……
          //验证和优化dex文件
          success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
                      doVerify, doOpt, &pClassLookup, NULL);
    ​
          ……
          //取消文件映射
          if (munmap(mapAddr, dexOffset + dexLength) != 0) {
              ALOGE("munmap failed: %s", strerror(errno));
              goto bail;
          }
          ……
    bail:
        dvmFreeRegisterMapBuilder(pRegMapBuilder);
        free(pClassLookup);
        return result;
    }
    

    可以看到,dvmContinueOptimization函数中对dex文件的加载便用了mmap内存映射函数。

    eventfd

    eventfd 是 Linux 2.6.22后才开始支持的一种IPC通信方式,它的作用主要时用来做事件通知,并且完全可以替代pipe,对于内核来说,eventfd的开销更低,eventfd只需要创建一个虚拟文件,而pipe需要创建两个,并且可用于select或epoll等多路复用模型中,来实现异步的信号通知功能。所以eventfd 是很好用的一种IPC方式,而且它的使用也简单。

    #include<sys/eventfd.h>  
    #include <unistd.h>
    int eventfd(unsigned int initval,int flags);//创建eventfd
    ssize_t write(int fd, const void *buf, size_t count);   //写数据
    ssize_t read(int fd, void *buf, size_t count);     //读数据
    

    eventfd在内核里的核心是一个计数器counter,它是一个uint64_t的整形变量counter,初始值为initval。

    当调用read() 函数读取eventfd时,会根据counter值执行下列操作:

    • 如果当前counter > 0,那么read返回counter值,并重置counter为0;
    • 如果当前counter等于0,那么read 函数阻塞直到counter大于0,如果设置了NONBLOCK,那么返回-1。

    当调用write() 往eventfd写数据时,我们只能写入一个64bit的整数value

    Eventfd在Android中的使用场景

    正是因为eventfd比管道更简单高效,所以在Android6.0之后,Looper的唤醒就换成了eventfd。

    Looper::Looper(bool allowNonCallbacks) :
            mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
            mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
            mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
        mWakeEventFd = eventfd(0, EFD_NONBLOCK);
        LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd.  errno=%d", errno);
    ​
        AutoMutex _l(mLock);
        rebuildEpollLocked();
    }void Looper::wake() {
    #if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ wake", this);
    #endif
        uint64_t inc = 1;
        ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
        if (nWrite != sizeof(uint64_t)) {
            if (errno != EAGAIN) {
                ALOGW("Could not write wake signal, errno=%d", errno);
            }
        }
    }
    

    可以看到,Looper的构造函数中mWakeEventFd已经由之前提到的pipe换成了evnentfd,wake()函数也不是之前的写入一个“w”字符,而是写入了一个64位整数1。

    Binder

    终于讲到Android的最后一种IPC的通信机制Binder了,有人会疑问,为什么AIDL,BroadCast,Content Provider这些不是Android的IPC机制呢?这些方式其实也是Android的IPC方式,但是他们的底层都是基于Binder实现的。

    Binder的机制比较复杂,由于这篇文章只是为了横向介绍Android的IPC机制,所以不会对Binder有太过深入的讲解,也不会在这儿介绍基于binder实现的BroadCast,Content Provider等IPC方式,这些我之后会写文专门讲解。我们主要了解一下Binder的架构及其设计思想。

    Linux已经有了前面提到的这么多的IPC方式了,为什么还要设计Binder呢?我觉得主要有三个原因的考虑。

    • 通信效率
    • 安全问题
    • 并发问题

    在上面提到的所有的IPC中,只有共享内存效率是最高,但是直接使用共享内存会有并发问题和安全问题

    我们先来解决安全问题。如果想要解决安全问题,我们可以采用C/S架构或者匿名的IPC通信机制,匿名的IPC通信机制无疑会影响进程间通信的方便性,比如匿名管道,就只能在亲属进程间通信。所以我们需要采用C/S的架构,在C/S架构下,Server端可以对Client端的请求做校验来保证安全性。

    接着我们需要解决并发问题,解决并发问题我们可以采用消息队列,或者通过锁来控制并发情况,或者采用C/S架构。

    在这三者的考虑下,我们发现只有采用C/S架构的共享内存才是最高效的。所以我们的Binder本质上就是C/S架构的共享内存的IPC机制。

    在这里插入图片描述

    从架构图可以看到,Binder其实是挂载在/dev/binder下的一个虚拟文件。我们可能猜测,Clinet端和Server端通过前面提到的mmap()文件映射函数,将/dev/binder下的文件映射到自己的用户空间中,这样Client端直接往这块内容写数据,Server也能同时读取文件了。

    但实际不是这样的。Binder的机制其实是通过将/dev/binder下的文件同时映射到Server端的用户空间和内核空间,在这种情况下,Server端想要读写这一块内容时,就不需要执行将数据从用户空间拷贝内核空间,或者将数据从内核空间拷贝到用户空间的操作了。我们的Client端只需要通过将数据写入内核空间,Server端的用户空间便能直接读取这块数据了。

    为什么binder的设计不采用上面这种方案,而采用下面的方案呢?因为上面的方案其实就是和共享内存的方案是一模一样了。采用下面的方案,Client写数据时,依然会陷入内核,内核函数此时可以充当Server的角色。

    总结

    自此,Android的IPC通信机制全部讲完了,受限于篇幅问题,有很多地方没有深入展开,比如Binder,如果深入展开又需要写非常长了。写这篇文章的目的,主要是想通过对Android IPC机制广度的认识,来达到更加深入思考的目的。比如为什么Android要设计Binder,Binder的优缺点是什么,如果让我们自己设计IPC,需要怎么设计?在我看来,我觉得Binder还是有一些缺点的,比如相比于Linux自身的IPC通信,它的内存占用过多,使用太过复杂,而且数据传输量有限制。又比如,Linux系统自带的IPC机制优缺点又是什么?它会往什么样的方向发展?2.6.22内核中为什么要新出eventfd这种IPC,接下来的内核中,又可能出现哪些IPC机制呢?Linux的图形操作系统中,如Ubuntu等系统的应用程序进行IPC时是采用的哪种IPC呢?

    有很多问题,我自己也不清楚。只能说技术这条路,路漫漫其修远兮,吾将上下而求索。


    欢迎关注个人技术公众号,坚持更新,坚持只写高质量文章,坚持探索技术的本质。
    在这里插入图片描述

    展开全文
  • 上一篇文章Android进程间通信(IPC)机制Binder简要介绍和学习计划简要介绍了Android系统进程间通信机制Binder的总体架构,它由Client、Server、Service Manager和驱动程序Binder四个组件构成。本文着重介绍组件...
  • Android 进程间通信——AIDL

    千次阅读 2018-01-12 19:56:11
    AIDL(Android Interface Definition Language)——进程间通信的一种机制。它允许您定义客户端和服务端通过使用进程间通信(IPC)进行通信的编程接口。在Android上,一个进程无法正常访问另一个进程的内存。所以说,...

    AIDL(Android Interface Definition Language)——进程间通信的一种机制。它允许您定义客户端和服务端通过使用进程间通信(IPC)进行通信的编程接口。在Android上,一个进程无法正常访问另一个进程的内存。所以说,他们需要将他们的对象分解成操作系统能够理解的原语,并且把这些对象放在你的边界上。编写这些代码非常繁琐,所以Android使用AIDL来处理它。

    Demo下载地址http://www.demodashi.com/demo/12321.html

    1 使用AIDL的必要条件

    • 只有当你需要来自不同应用的客户端通过IPC(进程间通信)通信来访问你的服务时,并且想在服务里处理多线程的业务,这时就需要使用AIDL。
    • 如果你不需要同时对几个应用进程IPC操作,你最好通过实现Binder接口来创建你的接口。
    • 如果你仍需要执行IPC操作,但不需要处理多线程,使用Messenger来实现接口即可。

    2 AIDL的使用

    使用Java编程语言语法在.aidl文件中定义您的AIDL接口,然后将其保存在承载服务的应用程序和任何其他绑定到该服务的应用程序的源代码(在src /目录中)。
    当应用程序构建包含.aidl文件时,Android SDK工具将生成一个基于.aidl文件的IBinder接口,并将其保存在项目的gen /目录中。 该服务必须适当地实现IBinder接口。 然后,客户端应用程序可以绑定到服务并从IBinder调用方法来执行IPC。

    使用AIDL 创建绑定的服务,具体步骤:

    1. 创建.aidl文件
      这个文件用方法签名来定义编程接口。
    2. 实现接口
      Android SDK工具根据你的.aidl文件以Java编程语言生成一个接口 。这个接口有一个名为Stub的内部抽象类,它继承了Binder并实现了AIDL接口中的方法。你必须继承这个 Stub类并实现这些方法。
    3. 将接口公开给客户端
      实现一个服务并重写onBind() 来返回你的Stub类的实现。

    2.1 创建.aidl文件

    AIDL使用简单的语法,可以用一个或多个方法(可以接收参数和返回值)来声明接口。参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。
    必须使用Java编程语言构建.aidl文件。 每个.aidl文件都必须定义一个接口,并且只需要接口声明和方法签名。

    默认情况下,AIDL支持以下数据类型:

    • Java编程语言中的所有基本类型(如int,long,char,boolean等)
    • String
    • CharSequence
    • List
      List中的所有元素都必须是支持的数据类型之一,或者是您声明的其他AIDL生成的接口或可接受的元素之一。 列表可以选择性地用作“通用”类(例如List )。 对方收到的实际具体类始终是一个ArrayList,尽管生成的方法是使用List接口。
    • Map
      Map中的所有元素都必须是此列表中受支持的数据类型之一,或者是您声明的其他AIDL生成的接口或可接受元素之一。 通用映射(如Map <String,Integer>形式的映射)不被支持。对方接收的实际具体类总是一个HashMap,尽管该方法是使用Map接口生成的。

    对于上面没有列出的每种附加类型,即使它们在与接口相同的包中定义,也必须包含一条import语句。

    在定义服务接口时,注意:

    • 方法可以采用零个或多个参数,并返回一个值或void。
    • 所有非原始参数都需要一个指向数据的方向标签。in,out或者inout(见下面的例子)。基本数据默认是in的,不能以其他方式。
      警告:您应该将方向限制在真正需要的地方,因为编组参数非常昂贵。
    • 包含在.aidl文件中的所有代码注释都包含在生成的IBinder接口中(导入和包装语句之前的注释除外)。
    • 只支持方法; 您不能在AIDL中公开静态字段。

    如下是一个.aidl 例子。IRemoteService.aidl

    package com.zpengyong.aidl;
    
    interface IRemoteService {
        void sendMessage(in String str);
    
        boolean play();
    
        boolean pause();
    
        boolean stop();
    }

    只需将.aidl文件保存在项目src/目录中,SDK工具会在项目gen/目录中生成IBinder接口文件。生成的文件名与.aidl文件名相匹配,但带有.java扩展名(例如IRemoteService.aidl结果IRemoteService.java)。

    2.2 实现接口

    IRemoteService.java接口文件包含一个名为Stub的类 ,它继承了Binder ,实现了IRemoteService接口,并声明.aidl文件中的所有方法。
    Stub还定义了一些辅助方法,最值得注意的是asInterface(),它接受一个IBinder(通常是传递给客户端的onServiceConnected()回调方法中的参数),并返回stub接口的一个实例。

    要实现从.aidl生成的接口,请继承生成的Binder接口(例如IRemoteService.Stub),并实现从.aidl文件继承的方法。
    下面是示例:

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){  
        public void sendMessage(String str){
            Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
            Message msg = new Message();
            msg.what = MSG_RECEIVE_MESSAGE;
            msg.obj = str;
            mHandler.sendMessage(msg);
        }
    
        public boolean play(){
            mService.play();
            return true;
        }
    
        public boolean pause(){
            mService.pause();
            return true;
        }
    
        public boolean stop(){
            mService.stop();
            return true;
        }
    };

    现在mBinder是Stub类的一个实例(一个Binder),它定义了服务的RPC接口。 在下一步中,这个实例被暴露给客户,以便他们可以与服务交互。

    在实现AIDL接口时,您应该注意一些规则

    • 传入的调用并不保证在主线程中执行,所以需要从头开始考虑多线程,并将服务正确地构建为线程安全的。
    • 默认情况下,RPC调用是同步的。如果您知道该服务需要超过几毫秒才能完成请求,则不应该从活动的主线程调用该服务,因为它可能会挂起应用程序(Android可能会显示“应用程序不响应”对话框,应该通常从客户端的一个单独的线程调用它们。
    • 抛出的任何异常都将被发回给调用者。

    2.3 将接口公开给客户端

    为了暴露你的服务的接口,扩展Service并实现onBind()返回实现生成的Stub的类的实例。 这里是一个示例服务,将IRemoteService示例接口公开给客户端。

    public class AIDLService extends Service {
        @Override
        public void onCreate() {
            super.onCreate();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            // Return the interface
            return mBinder;
        }
    
        private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
            public void sendMessage(String str){
                Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
                Message msg = new Message();
                msg.what = MSG_RECEIVE_MESSAGE;
                msg.obj = str;
                mHandler.sendMessage(msg);
            }
    
            public boolean play(){
                mService.play();
                return true;
            }
    
            public boolean pause(){
                mService.pause();
                return true;
            }
    
            public boolean stop(){
                mService.stop();
                return true;
            }
        };
    }

    现在,当一个客户端(比如一个activity)调用bindService()连接到这个服务时,客户端的onServiceConnected()回调会收到mBinder(服务onBind() 方法返回的 实例)。
    客户端还必须能够访问接口类,所以如果客户端和服务在不同的应用程序中,那么客户端的应用程序必须在其src/目录中拥有该.aidl文件的副本(这会生成android.os.Binder 接口 - 为客户端提供对AIDL方法的访问)。
    当客户端收到onServiceConnected()回调,得到IBinder,它必须调用 IRemoteService.Stub.asInterface(service)转换成IRemoteService类型。例如:

    private IRemoteService mIRemoteService;
    private ServiceConnection mConnection = new ServiceConnection() {
    
        // 当与服务端连接成功时,回调该方法。
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //转换
            mIRemoteService = IRemoteService.Stub.asInterface(service);
        }
    
        // 当与服务端连接异常断开时,回调该方法。
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mIRemoteService = null;
        }
    };

    3 调用IPC方法

    以下是调用类必须用来调用AIDL定义的远程接口的步骤:

    1. 将.aidl文件包含在项目src /目录中。
    2. 声明一个IBinder接口的实例(基于AIDL生成)。
    3. 实现ServiceConnection.
    4. 调用Context.bindService(),传入你的ServiceConnection实现。
    5. 在onServiceConnected()实现中,将收到一个IBinder实例。 调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回的参数强制转换为YourInterfaceName类型。
    6. 调用你在接口上定义的方法。 您应该始终捕获连接断开时引发的DeadObjectException异常; 这将是远程方法抛出的唯一异常。
    7. 要断开连接,调用Context.unbindService()。

    如下:

    package com.zpengyong.aidlclient;
    
    import com.zpengyong.aidl.IRemoteService;
    import com.zpengyong.aidl.IRemoteServiceCallback;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.text.Editable;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    public class MainActivity extends Activity implements OnClickListener {
        private final static String TAG = "MainActivity";
    
        private TextView mStateText, mMusicState;
        private Button mBtnHello, mBtnBind, mBtnStart, mBtnPause, mBtnStop;
        private EditText mTextMessage;
    
        private IRemoteService mIRemoteService;
    
        private final int STATE_DISCONNECTED = 1;
        private final int STATE_CONNECTING = 2;
        private final int STATE_CONNECTED = 3;
        private final int STATE_DISCONNECTING = 4;
        //与服务端的连接状态
        private int mBindState = STATE_DISCONNECTED;
    
        private ServiceConnection mConnection = new ServiceConnection() {
    
            // 当与服务端连接成功时,回调该方法。
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i(TAG, "onServiceConnected");
                mIRemoteService = IRemoteService.Stub.asInterface(service);
                mStateText.setText("connected");
                mBindState = STATE_CONNECTED;
                mBtnBind.setText("解绑");
                try {
                    mIRemoteService.registerCallback(mIRemoteServiceCallback);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            // 当与服务端连接异常断开时,回调该方法。
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.i(TAG, "onServiceDisconnected");
                mIRemoteService = null;
                mStateText.setText("disconnected");
                mBindState = STATE_DISCONNECTED;
                mBtnBind.setText("绑定");
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mStateText = (TextView) findViewById(R.id.connectState);
            mBtnHello = (Button) findViewById(R.id.sendMessage);
            mBtnBind = (Button)findViewById(R.id.bind);
            mBtnStart = (Button)findViewById(R.id.start_play);
            mBtnPause = (Button)findViewById(R.id.pause);
            mBtnStop = (Button)findViewById(R.id.stop_play);
            mBtnHello.setOnClickListener(this);
            mBtnStart.setOnClickListener(this);
            mBtnPause.setOnClickListener(this);
            mBtnStop.setOnClickListener(this);
            mBtnBind.setOnClickListener(this);
            mTextMessage = (EditText) findViewById(R.id.message);
            mMusicState = (TextView)findViewById(R.id.musicState);
        }
    
        private void bind() {
            mBindState = STATE_CONNECTING;
            Intent intent = new Intent();
            // Android 5.0 以上显示绑定服务
            intent.setComponent(new ComponentName("com.zpengyong.aidl", "com.zpengyong.aidl.AIDLService"));
            // 绑定服务
            this.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            mStateText.setText("connecting");
        }
    
        private void unbind() {
            mBindState = STATE_DISCONNECTING;
            try {
                mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mStateText.setText("disconnecting");
            //解除与Service的连接
            unbindService(mConnection);
            mBindState = STATE_DISCONNECTED;
            mStateText.setText("disconnected");
            mBtnBind.setText("绑定");
            mIRemoteService = null;
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if(mBindState != STATE_DISCONNECTED){
                unbind();
            }
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.sendMessage:
                String str = mTextMessage.getText().toString();
                if(str == null ||str.length() == 0)
                    return;
                if(mIRemoteService == null)
                    return;
                try {
                    mIRemoteService.sendMessage(str);
                } catch (RemoteException e1) {
                    e1.printStackTrace();
                }
                break;
            case R.id.bind:
                if(mBindState == STATE_DISCONNECTED){
                    bind();
                }else if(mBindState == STATE_CONNECTED){
                    unbind();
                }
                break;
            case R.id.start_play:
                if(mIRemoteService == null)
                    return;
                try {
                    boolean ret = mIRemoteService.play();
                    Log.i(TAG, "play ret="+ret);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.pause:
                if(mIRemoteService == null)
                    return;
                try {
                    boolean ret = mIRemoteService.pause();
                    Log.i(TAG, "pause ret="+ret);
                } catch (RemoteException e) {
                    e.printStackTrace();
                };
                break;
            case R.id.stop_play:
                if(mIRemoteService == null)
                    return;
                try {
                    boolean ret = mIRemoteService.stop();
                    Log.i(TAG, "stop ret="+ret);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
            }
        }
    }

    效果图如下:
    这里写图片描述

    4 服务端回调客户端

    如上的列子中只有客户端调用服务端的方法,并不能服务端调用客户端。

    在之前的IRemoteService.aidl文件中添加接口

    package com.zpengyong.aidl;
    import com.zpengyong.aidl.IRemoteServiceCallback;
    
    interface IRemoteService {
    
        void registerCallback(in IRemoteServiceCallback cb);
    
        void unregisterCallback(in IRemoteServiceCallback cb);
    
        void sendMessage(in String str);
    
        boolean play();
    
        boolean pause();
    
        boolean stop();
    }

    IRemoteServiceCallback.aidl中添加服务端调用客户端的接口。
    该文件服务度和客户端都需要包含该文件。

    package com.zpengyong.aidl;
    
    interface IRemoteServiceCallback {
        void stateChange(int value);
    }

    1 客户端实现回调接口
    要实现从IRemoteServiceCallback.aidl生成的接口,请继承生成的Binder接口(IRemoteServiceCallback.Stub),并实现从IRemoteServiceCallback.aidl文件继承的方法。

        private IRemoteServiceCallback mIRemoteServiceCallback = new IRemoteServiceCallback.Stub() {
    
            @Override
            public void stateChange(int value) throws RemoteException {
                Log.i(TAG, "stateChange value="+value);
                if(value == 1){
                    mMusicState.setText("开始播放");
                }else if(value == 2){
                    mMusicState.setText("暂停播放");
                }else if(value == 3){
                    mMusicState.setText("停止播放");
                }else if(value == 4){
                    mMusicState.setText("播放出错");
                }else {
                    mMusicState.setText("unknown");
                }
            }
        };

    2 注册回调
    客户端bindservice成功后会回调onServiceConnected,客户端可以获取到mIRemoteService,可以调用远端的放,这时可以通过调用远端方法注册回调接口实例。

        private ServiceConnection mConnection = new ServiceConnection() {
    
            // 当与服务端连接成功时,回调该方法。
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i(TAG, "onServiceConnected");
                mIRemoteService = IRemoteService.Stub.asInterface(service);
                mStateText.setText("connected");
                mBindState = STATE_CONNECTED;
                mBtnBind.setText("解绑");
                try {
                    //注册回调。
                    mIRemoteService.registerCallback(mIRemoteServiceCallback);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    }

    3 服务端保存回调接口
    由于AIDl支持多个客户端绑定,并处理并发请求。所以这里要将回调接口存到列表中,避免后注册的将前面注册的回调接口覆盖。

    //aidl支持多个客户端绑定,并且处理并发进程间通信,所以这里要存列表中。
    final RemoteCallbackList<IRemoteServiceCallback> mCallbackList
            = new RemoteCallbackList<IRemoteServiceCallback>();
    
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
    
        public void registerCallback(IRemoteServiceCallback cb){
            if(cb != null)mCallbackList.register(cb);
        }
    
        public void unregisterCallback(IRemoteServiceCallback cb){
            if(cb != null)mCallbackList.unregister(cb);
        }
        。。。
    }       

    4 服务器调用客户端方法
    遍历回调list,分别调用其stateChange方法,实现服务端调用客户端,实现双方通信。

     private void callstateChange(int value){
         //遍历保存的IRemoteServiceCallback,发送状态改变的消息。
         int num = mCallbackList.beginBroadcast();
         for(int i=0; i<num; i++){
             try {
                mCallbackList.getBroadcastItem(i).stateChange(value);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
         }
         mCallbackList.finishBroadcast();
     }

    当服务端调用回调接口的方法后,客户端的接口实现中就会收到响应。

    4 取消注册
    客户端unbindService前 调用取消注册的方法。

    private void unbind() {
        mBindState = STATE_DISCONNECTING;
        try {
            mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mStateText.setText("disconnecting");
        //解除与Service的连接
        unbindService(mConnection);
        mBindState = STATE_DISCONNECTED;
        mStateText.setText("disconnected");
        mBtnBind.setText("绑定");
        mIRemoteService = null;
    }

    客户端接收服务端的回调,效果显示如下:
    这里写图片描述

    展开全文
  • Android进程间通信机制(一)——基础篇 在正式了解Android的IPC机制之前我们了解以下几个问题: 什么是进程间通信 Android中一个应用实现多进程的方式 IPC基础:Serializable接口、Parcelable接口和Binder 一、...

    Android进程间通信机制(一)——基础篇

    在正式了解Android的IPC机制之前我们了解以下几个问题:

    • 什么是进程间通信
    • Android中一个应用实现多进程的方式
    • IPC基础:Serializable接口、Parcelable接口和Binder

    一、什么是进程间通信

    进程间通信(Inner-Process Comunication,简称IPC),就是指不同进程之间的信息传递。

    进程是系统进行资源分配和调度的基本单位,是操作系统的结构的基础;一个应用至少有一个进程(当然也可以拥有多个进程),一个进程中有包含了多个线程(线程是CPU调度的最小单位),进程相当于是线程的容器,线程可以使用操作系统分配个进程的资源。

    IPC机制是现代操作系统都存在的机制,而Android是基于Linux内核的OS,在Android中特有的方式Binder


    二、Android中创建多进程

    Android中在一个应用中创建多个进程的方式只有一种:在AndroidManifest.xml文件中申明四大组件的标签增加*“android:process=”""*属性即可。如下所示:

    1.私有进程

    <service
        android:name=".service.MyAIDLService"
        android:enabled="true"
        android:exported="true"
        android:process=":aidl_test" />
    

    2.全局进程

    <service
        android:name=".service.MyAIDLService"
        android:enabled="true"
        android:exported="true"
        android:process="com.kanlulu.aidl:aidl_test_test" />
    

    通过第一种方式创建的多进程以":“开头的”:aidl_test“(省略了主进程它的全称为"com.kanlulu.aidl_test:aidl_test”) 是私有进程,其他应用组件不能和它运行在同一个进程中。

    通过第二种写了完整名称的方式"com.kanlulu.aidl:aidl_test_test"是全局进程,可以使用相同ShareUID的方式运行在同一进程中(签名也需要一样)。

    使用多进程可以带来的好处:

    • 创建一个新的进程,可以获得更多的内存空间;
    • 主进程被杀死后,子进程任然可以运行(推送);
    • 子进程崩溃,主进程可以正常运行;

    使用多进程也会带来一些问题和需要注意事项:

    • 1.静态成员和单例模式完全失效;
    • 2.线程同步机制会完全失效;
    • 3.SharedPreferenced的可靠性会降低;
    • 4.Application会多次创建。

    首先要说明的是同一个应用中创建的子进程在内存中的地址和主进程是不一样的,所以第1条中静态成员在不同进程间修改的其实是位于不同内存地址的副本,单例模式也是一样的。第2条原因和第一条是类似的;至于第3条,SharedPreferenced的并发访问会导致不可预料的错误。另外需要注意第四条,创建子进程时也会重新创建一次Application。


    三、序列化Serializable接口和Parcelable接口

    序列化是指将运行时的对象转换成二进制,然后保存到流、内存或者通过网络传输给其他端。

    1、Serializable接口

    Serializable接口是Java提供的序列化接口。

    public interface Serializable {
    }
    

    下面是一个实现序列化接口的实体类:

    package com.kanlulu.aidl_test.bean;
    
    import java.io.Serializable;
    
    public class Persion implements Serializable {
        private String name;
        private int age;
        private String desc;
        private static final long serialVersionUID = 8829975621220483374L;
    
        public Persion() {
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    }
    
    

    我们只需要实现Serializable接口即可,另外还需要注意serialVersionUID属性;这个属性不是必须要创建的。

    serialVersionUID:

    简单来说它的作用就是用来序列化时的校验。只有当我们序列化前的serialVersionUID和我们反序列化是的serialVersionUID是一致的,反序列化才能成功,否则会报错:InvalidClassException

    如果我们不自己创建serialVersionUID,但我们在反序列化时更改了对象属性名称或类型时就会导致反序列化失败。

    serializable的序列化和反序列化分别通过ObjectOutputStreamObjectInputStream来实现的,代码如下:

    package com.kanlulu.aidl_test.utils;
    
    import android.text.TextUtils;
    import android.util.Log;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class SerialUtils {
        private static final String TAG = "SerialUtils";
    
        /**
         * 序列化
         *
         * @param object 序列化对象
         * @param path   序列化对象存储路径
         * @return
         */
        public synchronized static boolean saveObject(Object object, String path) {
            if (object == null || TextUtils.isEmpty(path)) {
                return false;
            }
            ObjectOutputStream outputStream = null;
            try {
                outputStream = new ObjectOutputStream(new FileOutputStream(path));
                outputStream.writeObject(object);
                outputStream.close();
                return true;
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            } finally {
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return false;
        }
    
        /**
         * @param path
         * @param <T>
         * @return
         */
        @SuppressWarnings("unchecked")
        public synchronized static <T> T readObject(String path) {
            if (TextUtils.isEmpty(path)) {
                return null;
            }
    
            ObjectInputStream inputStream = null;
            Object object = null;
            try {
                inputStream = new ObjectInputStream(new FileInputStream(path));
                object = inputStream.readObject();
            } catch (IOException | ClassNotFoundException e) {
                Log.e(TAG, e.getMessage());
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return (T) object;
        }
    }
    
    

    2、Parcelable接口

    Parcelable接口是Android所特有的序列化接口,在序列化中原始对象会被转换成Parcel对象;Android对于Parcel描述:

    Container for a message that can be sent through an IBinder.

    可以通过IBinder发送的消息的容器。

    package com.kanlulu.aidl_test.bean;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    /**
     * Created by kanlulu
     * DATE: 2018/11/30 15:54
     */
    public class Animal implements Parcelable {
        private String name;
        private int age;
        private String desc;
    
        public Animal() {
        }
    
        /**
         * 需要自己创建包含全部属性的构造方法
         */
        public Animal(String name, int age, String desc) {
            this.name = name;
            this.age = age;
            this.desc = desc;
        }
    
        /**
         * 自动生成的
         */
        protected Animal(Parcel in) {
            name = in.readString();
            age = in.readInt();
            desc = in.readString();
        }
    
        /**
         * 自动生成的
         * 反序列化
         */
        public static final Creator<Animal> CREATOR = new Creator<Animal>() {
            @Override
            public Animal createFromParcel(Parcel in) {
                return new Animal(in);
            }
    
            @Override
            public Animal[] newArray(int size) {
                return new Animal[size];
            }
        };
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        @Override
        public String toString() {
            return "Animal{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", desc='" + desc + '\'' +
                    '}';
        }
    
        @Override
        public int describeContents() {
            //几乎都返回 0,除非当前对象中存在文件描述符时为 1
            return 0;
        }
    
        /**
         * 序列化
         */
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeInt(age);
            dest.writeString(desc);
        }
    }
    
    

    Serializable和Parcelable的区别

    1.在使用的便捷性上来说,Serializable只需要实现该接口后额外创建一个serialVersionUID属性即可,而使用Parcelable接口需要实现四个方法,比较繁琐。

    2.通常如果我们需要保存数据到SD卡,或者需要进行网络传输数据,建议使用Serializable接口。

    3.如果我们需要传递内存中的数据,建议使用Parcelable。


    四、什么是Binder

    Android中有多种IPC机制,如AIDL、Messager、ContentProvider。而这些IPC机制的底层都是用Binder来实现的。

    Binder的设计使用的是Client-Server结构,客户端进程通过获取服务端进程的代理,并向代理接口方法中读写数据来完成进程间的通信。它的传输过程只需要一次拷贝,为发送添加UID/PID身份,安全性更高。

    对于Server而言,Binder可以看成是Server实现特定服务访问的接入地址,Client通过访问这个地址实现对Server的请求;对于Client而言,Binder可以看成是通向Server的管道入口,client要想和某个server通信首先必须建立管道和管道入口。

    与其他IPC不同,Binder使用面向对象的思想描述作为访问接入点的Binder和它在Client中的入口:Binder是一个实体位于Server中的对象,Client通过Binder的引用访问Server。

    Binder的通信框架定义了四个模型:Client、Server、ServiceManager和Binder驱动。其中Client、Server和ServiceManager运行在用户空间,Binder驱动运行在内核空间。他们的关系类似与互联网中:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。

    展开全文
  • RPC(Remote Procedure Call)即远程过程调用,它是一种通过网络从远程计算机程序上请求服务,在不需要了解底层网络技术的协议下,即可获取计算机进程中的数据。RPC使得开发包括网络分布式多程序在内的应用程序更加...

    什么是RPC

    RPC(Remote Procedure Call)即远程过程调用,它是一种通过网络从远程计算机程序上请求服务,在不需要了解底层网络技术的协议下,即可获取计算机进程中的数据。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

    RPC在OSI网络通信7层模型中,位于传输层应用层之间,即位于会话层
    这里写图片描述

    RPC实现模式,就是我们常说的C/S结构,引用百度百科的定义:

    RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

    这里写图片描述

    简而言之:客户端能向服务端发送若干个进程请求,服务端根据发送的进程参数依次返回对应的计算结果。RPC可以说客户端调用服务端的接口的过程,是面向接口的编程。

    RPC在Android中咋样体现的,我们看看官方的解释RPC与IPC的关系

    Android 利用远程过程调用 (RPC) 提供了一种进程间通信 (IPC) 机制,通过这种机制,由 Activity 或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。 这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。 然后,返回值将沿相反方向传输回来。 Android 提供了执行这些 IPC 事务所需的全部代码,因此您只需集中精力定义和实现 RPC 编程接口即可。
    要执行 IPC,必须使用 bindService() 将应用绑定到服务上。

    也就是说,RPC在的Android具体体现,是依赖 bindService()的方式,在onBind方法将服务端的计算结果返回给客户端(Activity等组件)的过程。

    什么是IPC

    IPC 即 Inter-Process Communication (进程间通信),是指进程间数据交互的过程。
    Android底层是基于Linux,而Linux基于安全考虑,是不允许两个进程间直接操作对方的数据,这就是进程隔离

    在Linux系统中,虚拟内存机制为每个进程分配了线性连续的内存空间,操作系统将这种虚拟内存空间映射到物理内存空间,每个进程有自己的虚拟内存空间,进而不能操作其他进程的内存空间,每个进程只能操作自己的虚拟内存空间,只有操作系统才有权限操作物理内存空间.进程隔离保证了每个进程的内存安全,但是在大多数情形下,不同进程间的数据通讯是不可避免的,因此操作系统必须提供跨进程通信机制。

    虽然Android是基于Linux,但并不能继承Linux中的进程通信的方式,Android有着自己进程间通信方式。常用有如下几种:

    下面引用Android开发艺术探索的总结,已经很全面了
    这里写图片描述

    参考

    • Android开发艺术探索
    • https://blog.csdn.net/u010132993/article/details/72582655
    • https://baike.baidu.com/item/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8%E5%8D%8F%E8%AE%AE/6893245?fromtitle=RPC&fromid=609861
    • https://developer.android.com/guide/components/processes-and-threads
    • https://blog.csdn.net/u011240877/article/details/72863432
    展开全文
  • 进程间通信简称IPC(Inter-Process Communication).Android 基于Linux,,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。这样做的目的是为了保证每个进程的安全。 IPC方式 名称...
  • Android进程间通信方式总结

    千次阅读 2019-04-30 09:07:49
    Android应用中使用多进程只有一个办法(用NDK的fork来做除外),就是在AndroidManifest.xml中声明组件时,用android:process属性来指定。 不知定process属性,则默认运行在主进程中,主进程名字为包名。 android:...
  •  我们知道,Android系统是基于Linux内核的,而Linux内核继承和兼容了丰富的Unix系统进程间通信(IPC)机制。有传统的管道(Pipe)、信号(Signal)和跟踪(Trace),这三项通信手段只能用于父进程与子进程之间,...
  • Android 进程间通信

    2017-07-05 00:32:34
    根据 Android 官方文档对 AIDL 的描述,做出的示例。免资源分下载!!!
  • 进程间通信基本原理 进程间通信的原理 Binder 的作用 Binder 的使用场景 Binder 是什么? 什么时候需要用到进程间通信? 为什么要多进程? 进程间通信为什么要用到Binder机制? 手机正在运行的进程 内存划分 ...
  • Android app常见使用多进程的场景 1.吃大内存的模块,如地图模块、大图浏览、webview等,Android对内存的限制是针对于进程的。 2.调用系统服务,比如电话,闹钟 Android app要用多进程的两个原因: 1.Android虚拟机...
  • Android进程间通信的几种方式

    万次阅读 2019-02-21 01:16:09
    Android应用中使用多进程只有一个办法(用NDK的fork来做除外),就是在AndroidManifest.xml中声明组件时,用android:process属性来指定。 不知定process属性,则默认运行在主进程中,主进程名字为包名。 android:...
  • Android 进程间通信AIDL demo 博客地址:http://blog.csdn.net/bigboysunshine/article/details/70228223
  • Android 进程间通信的几种实现方式

    万次阅读 2018-05-08 18:10:24
    在不同应用程序之间交互数据(跨进程通讯),在android SDK中提供了4种用于跨进程通讯的方式。这4种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。其中Activity...
  • AIDL----Android进程间通信(IPC)浅析测试代码,注释非常详细
  • 四种进程间通信的方式:activity 、service AIDL 、ContentProvider 、BroadcastReceiver 一个客户端一个服务端,相互通信

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 45,604
精华内容 18,241
关键字:

android进程间通信