fred-ye / summary

my blog
43 stars 9 forks source link

[Android] IPC 总结 #57

Open fred-ye opened 8 years ago

fred-ye commented 8 years ago

IPC

IPC(Inter-Process-Communication) 进程间通信。

当我们在启动一个app时,Zygote会申请一个进程,app便会运行在这个进程中。在Android的,每一个app默认会运行在一个进程中,有自己独立的VM。

进程内通信

举一个例子,当Activity和 Service之间的通信,可以声明一个接口, 如IFeedback, 它只有一个方法onFeedback(data), 在Activity中有一个内部类实现了这个接口,然后把这个接口 set到Service中,这样当service中有状态变化需要传到Activity时,通过回调这个接口的onFeedback将数据传回到Activity。当然还可以通过发广播的机制。

为什么一个App有多进程

在Android中使用多进程只有一种方式,那就是在四大组件中配置android:process属性。 配置进程的两种方式:

1.采用:这种方式,如

<service
    android:name=".ipc.DeviceService"
    android:process=":remote"/>

如果应用的包名是com.fred.repository, 后面运行的进程将会是com.fred.repository:remote: 这种形式会在当前进程前面加上包名。

2.直接写完整的进程名, 如:

<service
    android:name=".ipc.DeviceService"
    android:process="com.fred.repository.remote"/>

如果应用的包名是com.fred.repository, 后面运行的进程将会是com.fred.repository.remote

采用:开头的进程属于当前应用的私有进程,其它应用的组件不可以和它跑在同一个进程中。第二种方式指定的进程是属于全局进程,其它应用通过shareUserId 方式可以和它跑在同一个进程中

由此带来的问题

Android系统对每一个进程分配有不同的虚拟机,因此在多进程模式下一不留神就会带来一些数据上的问题。由于不在同一个进程中,线程同步机制也就无用了,SharedPreferences的可靠性下降,Application会多次创建。

基础

Android 框架的IPC沟通依赖单一的IBinder接口,Client端调用IBinder接口的transact函数,通过IPC机制而调用到服务端(Remote)的onTransact函数 看一下IBinder:

/**
 * Perform a generic operation with the object.
 *
 * @param code The action to perform.  This should
 * be a number between {@link #FIRST_CALL_TRANSACTION} and
 * {@link #LAST_CALL_TRANSACTION}.
 * @param data Marshalled data to send to the target.  Must not be null.
 * If you are not sending any data, you must create an empty Parcel
 * that is given here.
 * @param reply Marshalled data to be received from the target.  May be
 * null if you are not interested in the return value.
 * @param flags Additional operation flags.  Either 0 for a normal
 * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
 */
public boolean transact(int code, Parcel data, Parcel reply, int flags)throws RemoteException;

Android 中有两个实现类,因为跨进程,故两边一边一个。分别是BinderBinderProxy, 这两个类都存在于Binder.java中。Binder对应的是Service, BindProxy对应的是客户端。

IPC通信的通信三个步骤:

  1. Activity使用StartActivity
  2. bindService绑定Service,建立Activity和Service之间的连接
  3. Activity调用IBinder接口的transact函数,透过底层的Binder Driver驱动而间接调用到Binder基类的execTransact函数,从而调用到具体类的onTransact, 看下面的例子

    采用Binder进行跨进程通信

public class MyBinder extends Binder {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case 1:
                String obj = "play";
                Message msg = MyService.handler.obtainMessage(1, 1, 1, obj);
                MyService.handler.sendMessage(msg);
                break;
            case 2:
                obj = "stop";
                msg = MyService.handler.obtainMessage(1, 1, 1, obj);
                MyService.handler.sendMessage(msg);
                break;
        }
        return true;
    }
}
public class MyService extends Service implements Runnable {
    private IBinder mBinder = null;
    private Thread thread;
    public static Handler handler;
    public static Context ctx;

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        ctx = this;
        mBinder = new MyBinder();
        thread = new Thread(this);
        thread.start();
    }

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

    @Override
    public void run() {
        Looper.prepare();
        handler = new EventHandler(Looper.myLooper()); Looper.loop();
        Looper.loop();
    }
    class EventHandler extends Handler {
        public EventHandler(Looper looper) {
            super(looper);
        }
        public void handleMessage(Message msg) {
            String obj = (String)msg.obj;
            if (obj.contains("play")) {
                System.out.println("play------>" + Thread.currentThread().getName());
            } else {
                System.out.println("stop------>");
            }
        }
    }
}
public class TestIPCBinderActivity extends Activity{
    private IBinder ib = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        bindService( new Intent(TestIPCBinderActivity.this, MyService.class), mConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            ib = binder;
            test();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    public void test() {
        final Parcel data = Parcel.obtain();
        final Parcel reply = Parcel.obtain();
        try {
            ib.transact(1, data, reply, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    ib.transact(2, data, reply, 0);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 3000);
    }
}

采用Binder + Proxy/Stub模式实现跨进程通信

对于每一次的跨进程调用,我们都需要在客户端调用binder的transact方法,然后通过拦截服务端的onTransact来进行处理。当前的例子比较简单,只有playstop两种形为,如果客户端有较多的行为,我们就需要在onTransact中对参数做很多的判断。再来看如何利用Proxy/Stub模式来简化解决这个问题。

public interface IPlayer {
    void play();
    void stop();
    String getStatus();
}
public class PlayerProxy implements IPlayer {
    private IBinder binder;
    private String status;

    PlayerProxy(IBinder binder) {
        this.binder = binder;
    }

    @Override
    public void play() {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeString("play");
        try {
            binder.transact(1, data, reply, 0);
            status = reply.readString();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void stop(){
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeString("stop");
        try {
            binder.transact(2, data, reply, 0);
            status = reply.readString();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getStatus() {
        return status;
    }

}
public abstract class PlayerStub extends Binder implements IPlayer {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        reply.writeString(data.readString() + " mp3");
        if (code == 1) {
            this.play();
        } else if(code == 2) {
            this.stop();
        }
        return true;
    }
    public abstract void play();
    public abstract void stop();
}
public class PlayerBinder extends PlayerStub {
    private MediaPlayer player = null;
    private Context context;
    public PlayerBinder(Context context) {
        this.context = context;
    }
    @Override
    public void play() {
        if (player != null) {
            return;
        }
        player = MediaPlayer.create(context, R.raw.shake_match);
        player.start();
    }

    @Override
    public void stop() {
        if(player != null) {
            player.stop();
            player.release();
            player = null;
        }
    }

    @Override
    public String getStatus() {
        return null;
    }
}
public class PlayerRemoteService extends Service {
    private IBinder binder = null;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public void onCreate() {
        binder = new PlayerBinder(getApplicationContext());
    }
}
public class TestProxyStubActivity extends Activity {
    public static final String TAG = TestProxyStubActivity.class.getSimpleName();
    private PlayerProxy proxy = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = new Intent(this, PlayerRemoteService.class);
        startService(intent);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            proxy = new PlayerProxy(service);
            proxy.play();
            Log.i(TAG, "[status]" + proxy.getStatus());
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    proxy.stop();
                    Log.i(TAG, "[status]" + proxy.getStatus());
                }
            }, 3000);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
}

采用AIDL进行通信的例子

AIDL的目的是定义Proxy/Stub来封装IBinder接口,以便开发者更方便的使用。IBinder接口中只有transact来进行ipc通信,不方便实际开发。[Proxy对应的是客户端,Stub 是服务端]

用一个例子来说明IPC通信。假设有这么一个需求,在我们的App中会调用到外部设备的一个功能,这个设备采用的也是Android系统,设备有一个红外扫描的功能。当我们调用这个功能,会让扫描功能运行在一个独立的进程中,扫描成功后将数据传递给某个Activity。

package com.fred.repository.ipc;
import android.util.Log;

//模拟外设
public class Device {
    public final String TAG  = this.getClass().getSimpleName();
    private boolean isOpen = false;
    public void init () {
        Log.i(TAG, "[init]");

    }
    public void powerOn() {
        Log.i(TAG, "[power on]");
        isOpen = true;
    }

    public int startScan() {
        Log.i(TAG, "[start scan]");
        int d = (int) (Math.random() * 1000);
        return d;
    }

    public void powerOff() {
        Log.i(TAG, "[power off]");
        isOpen = false;
    }
    public boolean isOpen() {
        return isOpen;
    }
}
package com.fred.repository.ipc;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * Created by fred on 16/7/13.
 */
public class DeviceService extends Service {
    public final String TAG  = this.getClass().getSimpleName();

    private Device device;

    private final IDeviceAidl.Stub binder = new IDeviceAidl.Stub() {
        @Override
        public void onDeviceServiceConnected() throws RemoteException {
            device = new Device();
            device.init();
        }

        @Override
        public void onDeviceServiceDisconnected() throws RemoteException {
            Log.i(TAG, "device connect error!");
        }

        @Override
        public int startScan() throws RemoteException {
            return device.startScan();
        }

        @Override
        public void powerOn() throws RemoteException {
            device.powerOn();
        }

        @Override
        public void powerOff() throws RemoteException {
            device.powerOff();
        }

        @Override
        public boolean isDeviceOpen() throws RemoteException {
            return device.isOpen();
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}
package com.fred.repository.ipc;

import android.app.Service;
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.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import com.fred.repository.R;

public class TestIPCActivity extends AppCompatActivity {
    public final String TAG  = this.getClass().getSimpleName();
    Intent serviceIntent;

    public static Context contextTemp;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_ipc);
        initView();
        contextTemp = TestIPCActivity.this;
    }

    private void initView() {
        findViewById(R.id.btn_init).setOnClickListener(listener);
    }
    private View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                if (!remoteService.isDeviceOpen()) {
                    remoteService.powerOn(); //打开设备
                }
                int result = remoteService.startScan(); // 开始扫描
                Log.i(TAG, "[result] " + result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }
    };

    @Override
    protected void onResume() {
        super.onResume();
        serviceIntent = new Intent(TestIPCActivity.this, DeviceService.class);
        startService(serviceIntent);
        bindService(serviceIntent, conn, Service.BIND_AUTO_CREATE);
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            if (!remoteService.isDeviceOpen()) {
                remoteService.powerOff();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        unbindService(conn);
        stopService(serviceIntent);
    }

    public IDeviceAidl remoteService;
    public ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "----Service Disconnected----");
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "----Service Connected!----" + service.getClass().toString());
            remoteService = IDeviceAidl.Stub.asInterface(service);
            try {
                remoteService.onDeviceServiceConnected();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };
}
<service
    android:name=".ipc.DeviceService"
    android:process=":remote"/>

总结一下 采用AIDL进行通信的流程:

  1. 创建Service和AIDL接口
  2. 在Service类中创建一个AIDL.Stub类的对象,该对象中实现了AIDL中定义的抽象方法,然后在Service的onBind方法中返回这个类的对象
  3. 客户端绑定服务端Service,建立连接后就可以远程访问服务端的方法。

    使用Bundle进行跨进程的通信

这个其实很好理解,Bundle是传递数据的通道,当我们在App中需要集成QQ,微信的功能时,我们通常会发一个intent, 将我们业务相关的数据利用Bundle封装起来存放到intent中, 然后再启动了QQ或微信的某个Activity, 这便是一种很典型的利用Bundle进行跨进程通信的例子。

采用Messager进行跨进程通信

采用Messager进行跨进程通信,其底层其实还是采用AIDL, 关键地方已在代码中加上注释

public class MessengerService extends Service {
    public static final String TAG = "MessengerService";
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case CanstantsUtil.MSG_FROM_CLIENT:
                    String data = msg.getData().getString("msg");
                    Log.i(TAG, "[Message From client]" + data);
                    //获取client
                    Messenger client = msg.replyTo;
                    //设置回复给client的消息
                    Message replyMessage = Message.obtain(null, CanstantsUtil.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString("feedback", "Get your hello");
                    replyMessage.setData(bundle);
                    try {
                        client.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    private final Messenger mMessenger = new Messenger(new MessengerHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
public class TestMessengerActivity extends Activity {
    public static final String TAG = "TestMessengerActivity";
    private Messenger messenger;
    private ServiceConnection mConnnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            messenger = new Messenger(service);
            Message msg = Message.obtain(null, CanstantsUtil.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg", "Hello");
            msg.setData(data);
            //设置处理service回复的handler.
            msg.replyTo = new Messenger(handler);
            try {
                messenger.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    private static Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case CanstantsUtil.MSG_FROM_SERVICE:
                    Log.i(TAG, "[Message From Service]" + msg.getData().getString("feedback"));
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_messenger);
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        unbindService(mConnnection);
        super.onDestroy();
    }
}
public class CanstantsUtil {
    public static final int MSG_FROM_CLIENT = 1;
    public static final int MSG_FROM_SERVICE = 2;
}
<service android:name=".ipc.messenger.MessengerService"
  android:process=":messenger" />
08-05 11:19:46.984 21276-21276/fred.com.repository:messenger I/MessengerService: [Message From client]Hello
08-05 11:19:47.072 21251-21251/com.fred I/TestMessengerActivity: [Message From Service]Get your hello

总结一下

暂放

使用Socket进行跨进程通信

原理:在Service中会创建一个ServerSocket来监听客户端连接,这个Service运行在一个单独的进程中,在客户端(如Activity)中会创建一个Socket,让两者进行通信,用来交换数据。