《Android开发艺术探索》第二章重点笔记

                                                                                           第二章  IPC机制    

##1、多进程模式的运行机制
          Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在不同的内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。
一般来说,使用多进程会造成如下几个方面的问题:

(1)静态成员和单例模式完全失效
(2)线程同步机制完全失效 ,不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象
(3)SharedPreference的可靠性下降
SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这时因为SharedPreferences底层是通过读写XML文件来实现的,并发写显然是可能出问题的,甚至并发读写都有可能发生问题
(4)Application会多次创建
运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的。同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application的。

##2、Binder
   (1)继承了IBinder接口
   (2) Binder是一种跨进程通信方式
   (3)是ServiceManager连接各种Manager(ActivityManager,WindowManager等)和相应ManagerService的桥梁
   (4)从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务器会返回一个包含了服务器端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者是数据,这里的服务包含了普通服务和基于AIDL的服务aidl工具根据aidl文件自动生成的java接口的解析:首先,它声明了几个接口方法,同时还声明了几个整型的id用于标识这些方法,id用于标识在transact过程中客户端所请求的到底是哪个方法;接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub内部的代理类Proxy来完成。 所以,这个接口的核心就是它的内部类Stub和Stub内部的代理类Proxy。
  下面分析其中的方法:

        &  asInterface(android.os.IBinder obj):用于将服务器端的Binder对象转化成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端是在同一进程中,那么这个方法返回的是服务端的Stub对象本身,否则返回的是系统封装的Stub.Proxy对象。

       &  asBinder:返回当前Binder对象

       &  onTransact:这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。 这个方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags) 服务端通过code可以知道客户端请求的目标方法,接着从data中取出所需的参数,然后执行目标方法,执行完毕之后,将结果写入到reply中。如果此方法返回false,说明客户端的请求失败,利用这个特性可以做权限验证(即验证是否有权限调用该服务)。

      & Proxy#[Method]:代理类中的接口方法,这些方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:首先创建该方法所需要的参数,然后把方法的参数信息写入到_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。

##3、Android的IPC方式
         1、Bundle
         2、文件共享
         3、使用Messenger
         4、使用AIDL
         5、使用ConntentProvider
         6、使用Socket

1、使用Bundle
      Activity,Service,Receiver都是支持在Intent中传递Bundle数据,由于Bundle实现了Parcelable接口,所以方便的在不同的进程间传输。

2、使用文件共享
     两个进程通过读写同一个文件交换数据,Android基于Linux系统。使其并发读写文件可以没有限制的进行,甚至两个线程同时对同一个文件进行读写操作都是允许的。通过共享的文件也是有局限性的,比如并发读写的问题,如果并发读,那么我们读出的内容具有可能不是最新的,因此文件共享方式是个对数据同步要求不高的进程之间通信。
     本质上来说SharedPreferences属于文件的一种,由于系统对他的读写有一定的缓存策略,即在内存中会有一份SharedPreference文件缓存,因此在多进程模式下,系统对他的读写变得不可靠,当面对高并发访问,SharedPreferences很大几率丢失数据,因此不建议在进程中通信使用SharedPreferences.

//写
private void persistToFile() {
    new Thread(new Runnable() {
        public void run() {
            User user = new User(1, "hello world", false);
            File dir = new File(MyConstants.CHAPTER_2_PATH);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
            ObjectOutputStream objectOutputStream = null;
            try {
                objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
                objectOutputStream.writeObject(user);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                MyUtils.close(objectOutputStream);
            }
        }
    }).start();
}

//读

private void recoverFromFile() {
    new Thread(new Runnable() {
        public void run() {
            User user = null;
            File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
            if (cachedFile.exists()) {
                ObjectInputStream objectInputStream = null;
                try {
                    objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
                    user = (User) objectInputStream.readObject();
                    Log.d(TAG, "recover user:" + user);
                } catch (Exception e) {
                    e.printStackTrace();              
                } finally {
                    MyUtils.close(objectInputStream);
                }
            }
        }
    }).start();
}

 

3、使用Messenger
    Messenger对AIDL做了封装,底层实现就是AIDL,方便进程间通信,由于一次处理一个请求,因此在服务端不用考虑线程同步问题,不存在并发执行的情形。下面是Messenger的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它的底层是AIDL。

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

    public Messenger(IBinder target){
        mTarget = IMessenger.Stud.asInterface(target);
    }


    接下里看下使用步骤
    客户端:
    1.在activity中绑定服务
    2.创建ServiceConnection并在其中使用 IBinder 将 Messenger实例化 
    3.使用Messenger向服务端发送消息
    4.解绑服务
    5.服务端中在 hadleMessage() 方法中接收每个 Message
    服务端:
     1、建一个handler对象,并实现hanlemessage方法,用于接收来自客户端的消息,并作处理
     2、messenger(送信人),封装handler 
     3、messenger的getBinder()方法获取一个IBinder对象,通过onBind返回给客户端

上面实现的仅仅是单向通信,即客户端给服务端发送消息,如果我需要服务端给客户端发送消息又该怎样做呢?
下面就让我们接着上面的步骤来实现双向通信吧
1.在客户端中创建一个Handler对象,用于处理服务端发过来的消息
2.创建一个客户端自己的messenger对象,并封装handler。
3.将客户端的Messenger对象赋给待发送的Message对象的replyTo字段
4.在服务端的Handler处理Message时将客户端的Messenger解析出来,并使用客户端的Messenger对象给客户端发送消息

接下来看一下代码:

//客户端
在Messenger中进行数据传递必须将数据放入Messsage中,而Messenger和Message都实现了Parcelable接口,实际上,通过 Messenger来传递Message,Message中能使用的载体就只有what、arg1、arg2、Bundle以及replyTo,我们还有Bundle,Bundle中可以支持大量的数据类型。

public class MainActivity extends AppCompatActivity {   
    private Messenger mService;
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());

private static class MessengerHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case MyConstants.MSG_FROM_SERVICE:
           //接收从服务端传递来的数据
            Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
            break;
        default:
            super.handleMessage(msg);
        }
    }
}

private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
        mService = new Messenger(service);
        Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
        Bundle data = new Bundle();
        data.putString("msg", "hello, this is client.");
        msg.setData(data);
      //当客户端发送消息时需要把接收服务端返回的Messenger通过Message的replyTo参数传递给服务端
        msg.replyTo = mGetReplyMessenger;
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    public void onServiceDisconnected(ComponentName className) {}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_messenger);
    Intent intent = new Intent("com.ryg.MessengerService.launch");
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
}

//服务端service
public class MessengerService extends Service {   
   
     private static class MessengerHandler extends Handler {
         public void handleMessage(Message msg) {

        switch (msg.what) {
        case MyConstants.MSG_FROM_CLIENT:
            Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
            //收到消息后立即回复一条消息给客户端
            Messenger client = msg.replyTo;
            Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
            Bundle bundle = new Bundle();
            bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
            relpyMessage.setData(bundle);
            try {
                client.send(relpyMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            break;
        default:
            super.handleMessage(msg);
        }
    }
}

private final Messenger mMessenger = new Messenger(new MessengerHandler());

@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
  }
}


4、使用AIDL
     使用AIDL进行进程间通信分为服务端和客户端两方面:
    (1)、服务端
     服务端首先创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个接口。

    (2)、客户端
    绑定服务端的Service,绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
   
   (3)、AIDL接口的创建
    新建一个后缀为AIDL的文件,声明一个接口和方法在gen目录下自动生成对应的.java文件
   interface IService {
       void callALiPayService();
    }
   AIDL文件中并不是所有的数据类型都支持的,那么支持数据的类型有:
         基本数据类型(int,long,char,boolean等)
         String和CharSequence
         List:只支持ArrayList
         Map:只支持HashMap
         Parcelable:所有实现Parcelable接口的对象
         AIDL:所有AIDL接口本身也可以在AIDL文件中使用

     (4)远程服务端Service的实现

public class ALiPayService extends Service {

    public void onCreate() {
        super.onCreate();
    }

    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(),"绑定成功,准备付费",Toast.LENGTH_LONG).show();
        return new MyBinder();
    }

    public class MyBinder extends IService.Stub{
        public void callALiPayService() throws RemoteException {
            Toast.makeText(getApplicationContext(),"开始付费,购买装备",Toast.LENGTH_LONG).show();
        }
    }

    public boolean onUnbind(Intent intent) {
        Toast.makeText(getApplicationContext(),"解除绑定,取消付费",Toast.LENGTH_LONG).show();
        return super.onUnbind(intent);
    }

    public void onDestroy() {
        super.onDestroy();
    }


     (5)客户端的实现
   绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后可以通过这个接口去调用服务端的远程方法了。

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private MyConn myConn;
    private Intent intent;
    private IService iService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intent = new Intent();
        intent.setAction("cn.alipay");//清单文件中指定的action动作
        intent.setPackage("com.alipayservice");
        findViewById(R.id.bind).setOnClickListener(this);
        findViewById(R.id.call).setOnClickListener(this);
        findViewById(R.id.unbind).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bind: //点击执行绑定的动作
                myConn = new MyConn();
                bindService(intent,myConn,BIND_AUTO_CREATE);
                break;
            case R.id.call://点击执行支付的动作
                try {
                    iService.callALiPayService();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.unbind://点击执行解绑的动作
                unbindService(myConn);
                break;
        }
    }

    public class MyConn implements ServiceConnection{
        public void onServiceConnected(ComponentName name, IBinder service) {
            iService = IService.Stub.asInterface(service);
        }
        public void onServiceDisconnected(ComponentName name) {}
    }
}


5、使用ContentProvider
      继承ContentProvider,实现6个抽象方法:onCreate,query,update,insert,delete,getType
     (1)ContentProvider主要以表格的形式来组织数据,并且可以包含多个表; 
     (2)ContentProvider还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider; 
     (3)ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;
     (4)要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者;

6、使用Socket套接字,分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中TCP和UDP协议。
          TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过"三次握手"才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传功能,因此具有很高的稳定性
         UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能,在性能上,UDP具有更好的效率,其缺点是不保证数据能够正确传输,尤其是在网络拥塞的情况下。

TCP下服务端设计:当serve启动在线程中建立TCP服务,等待客户端连接请求。当有客户端连接时生成一个新的socket,每次创建的socket分别和不同的客户端通信,服务端收到消息回复。当客户端断开连接时,服务端通过判断输入流返回值为null,关闭对应的socket并结束通话线程。
主要代码如下:

public class TCPServerService extends Service {

    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

 
    private class TcpServer implements Runnable {

        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(8688);
            } catch (Exception e) {
                return;
            }

            while (!mIsServiceDestoryed) {
                try {
                    // 接受客户端请求
                    final Socket client = serverSocket.accept();
                    System.out.println("accept");
                    new Thread() {
                        @Override
                        public void run() {
                            try {
                                responseClient(client);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        };
                    }.start();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void responseClient(Socket client) throws IOException {
        // 用于接收客户端消息
        BufferedReader in = new BufferedReader(new InputStreamReader(
                        client.getInputStream()));
        // 用于向客户端发送消息
        PrintWriter out = new PrintWriter(new BufferedWriter(
                new OutputStreamWriter(client.getOutputStream())), true);
        out.println("欢迎来到聊天室!");
        while (!mIsServiceDestoryed) {
            String str = in.readLine();
            if (str == null) {
                break;
            }
            int i = new Random().nextInt(mDefinedMessages.length);
            String msg = mDefinedMessages[i];
            out.println(msg);
        }
        out.close();
        in.close();
        client.close();
    }
}

TCP下客户端设计:客户端Activity启动时在onCreate中开启一个线程连接服务端socket,采用超时重连的策略,间隔为1000毫秒,服务端连接成功后就可以通信了,当Activity退出时就退出循环并终止线程,关闭socket。
主要代码如下:
 

public class TCPClientActivity extends Activity implements OnClickListener {

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MESSAGE_RECEIVE_NEW_MSG: {
                mMessageTextView.setText(mMessageTextView.getText()
                        + (String) msg.obj);
                break;
            }
            case MESSAGE_SOCKET_CONNECTED: {
                mSendButton.setEnabled(true);
                break;
            }
            default:
                break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tcpclient);
        Intent service = new Intent(this, TCPServerService.class);
        startService(service);
        new Thread() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }.start();
    }

    private void connectTCPServer() {
        Socket socket = null;
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                System.out.println("connect server success");
            } catch (IOException e) {
                SystemClock.sleep(1000);
                System.out.println("connect tcp server failed, retry...");
            }
        }

        try {
            // 接收服务器端的消息
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
            while (!TCPClientActivity.this.isFinishing()) {
                String msg = br.readLine();
                System.out.println("receive :" + msg);
                if (msg != null) {
                    String time = formatDateTime(System.currentTimeMillis());
                    final String showedMsg = "server " + time + ":" + msg
                            + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)
                            .sendToTarget();
                }
            }
            System.out.println("quit...");
            mPrintWriter.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

总结:IPC方式对比

Bundle:          优点:简单易用                 缺点:只传输Bundle支持数据类型                        适用场景:四大组件间进程通信

文件共享:       优点:简单易用                  缺点:不适合高并发,无法做到进程间即时通信     适用场景:无并发,实时性不高的场景

AIDL:             优点:一对多并发实时通信      缺点:使用复杂,处理好线程同步                     适用场景:一对多通信且有RPC需求

Messenger:   优点:一对多并发实时通信      缺点:不适合高并发,只传输Bundle支持数据类型    适用场景:低并发一对多通信

ContentProvider:    优点:一对多并发数据共享         缺点:提供数据源CRUD操作            适用场景:一对多进程间数据共享

Socket:          优点:网络传输字节流,一对多并发实时通信         缺点:细节繁琐                  适用场景:网络数据交换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值