1. 程式人生 > >Android 進程間通信

Android 進程間通信

char lag in use 做了 eui 內存 rman soc 分享

什麽鬼!單例居然失效了,一個地方設置值,另個地方居然取不到,這怎麽可能?沒道理啊!排查半天,發現這兩就不在一個進程裏,才恍然大悟……

什麽是進程

按照操作系統中的描述:進程一般指一個執行單元,在 PC 和移動設備上指一個程序或者一個應用。

為什麽要使用多進程

我們都知道,系統為 APP 每個進程分配的內存是有限的,如果想獲取更多內存分配,可以使用多進程,將一些看不見的服務、比較獨立而又相當占用內存的功能運行在另外一個進程當中。

目錄結構預覽

先放出最終實踐後的目錄結構,有個大概印象,後面一一介紹。
技術分享圖片

技術分享圖片

如何使用多進程

AndroidManifest.xml 清單文件中註冊 Activity、Service 等四大組件時,指定 android:process 屬性即可開啟多進程,如:

<activity
    android:name=".Process1Activity"
    android:process=":process1" />
<activity
    android:name=".Process2Activity"
    android:process="com.wuxiaolong.androidprocesssample.process2" />

技術分享圖片
說明

1、com.wuxiaolong.androidprocesssample,主進程,默認的是應用包名;

2、android:process=":process1",“:”開頭,是簡寫,完整進程名包名 + :process1

3、android:process="com.wuxiaolong.androidprocesssample.process2",以小寫字母開頭的,屬於全局進程,其他應用可以通過 ShareUID 進行數據共享;

4、進程命名跟包名的命名規範一樣。

進程弊端

Application 多次創建

我們自定義一個 Application 類,onCreate 方法進行打印 Log.d("wxl", "AndroidApplication onCreate");

,然後啟動 Process1Activity:

com.wuxiaolong.androidprocesssample D/wxl: AndroidApplication onCreate
com.wuxiaolong.androidprocesssample:process1 D/wxl: AndroidApplication onCreate

看到確實被創建兩次,原因見:android:process 的坑,你懂嗎?多數情況下,我們都會在工程中自定義一個 Application 類,做一些全局性的初始化工作,因為我們要區分出來,讓其在主進程進行初始化,網上解決方案:

@Override
public void onCreate() {
    super.onCreate();
    String processName = AndroidUtil.getProcessName();
    if (getPackageName().equals(processName)) {
        //初始化操作
        Log.d("wxl", "AndroidApplication onCreate=" + processName);
    }
}

AndroidUtil:

public static String getProcessName() {
    try {
        File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
        BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
        String processName = mBufferedReader.readLine().trim();
        mBufferedReader.close();
        return processName;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

靜態成員和單例模式失效

創建一個類 SingletonUtil:

public class SingletonUtil {
    private static SingletonUtil singletonUtil;
    private String userId = "0";

    public static SingletonUtil getInstance() {
        if (singletonUtil == null) {
            singletonUtil = new SingletonUtil();
        }
        return singletonUtil;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

在 MainActivity 進行設置:

SingletonUtil.getInstance().setUserId("007");

Process1Activity 取值,打印:

Log.d("wxl", "userId=" + SingletonUtil.getInstance().getUserId());

發現打印 userId=0,單例模式失效了,因為這兩個進程不在同一內存了,自然無法共享。

進程間通信

文件共享

既然內存不能共享,是不是可以找個共同地方,是的,可以把要共享的數據保存 SD 卡,實現共享。首先將 SingletonUtil 實現 Serializable 序列化,將對象存入 SD 卡,然後需要用的地方,反序列化,從 SD 卡取出對象,完整代碼如下:

SingletonUtil

public class SingletonUtil implements Serializable{
    public static String ROOT_FILE_DIR = Environment.getExternalStorageDirectory() + File.separator + "User" + File.separator;
    public static String USER_STATE_FILE_NAME_DIR = "UserState";
    private static SingletonUtil singletonUtil;
    private String userId = "0";

    public static SingletonUtil getInstance() {
        if (singletonUtil == null) {
            singletonUtil = new SingletonUtil();
        }
        return singletonUtil;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

序列化和反序列化

public class AndroidUtil {
    public static boolean createOrExistsDir(final File file) {
        // 如果存在,是目錄則返回true,是文件則返回false,不存在則返回是否創建成功
        return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
    }

    /**
     * 刪除目錄
     *
     * @param dir 目錄
     * @return {@code true}: 刪除成功<br>{@code false}: 刪除失敗
     */
    public static boolean deleteDir(final File dir) {
        if (dir == null) return false;
        // 目錄不存在返回true
        if (!dir.exists()) return true;
        // 不是目錄返回false
        if (!dir.isDirectory()) return false;
        // 現在文件存在且是文件夾
        File[] files = dir.listFiles();
        if (files != null && files.length != 0) {
            for (File file : files) {
                if (file.isFile()) {
                    if (!file.delete()) return false;
                } else if (file.isDirectory()) {
                    if (!deleteDir(file)) return false;
                }
            }
        }
        return dir.delete();
    }

    /**
     * 序列化,對象存入SD卡
     *
     * @param obj          存儲對象
     * @param destFileDir  SD卡目標路徑
     * @param destFileName SD卡文件名
     */
    public static void writeObjectToSDCard(Object obj, String destFileDir, String destFileName) {

        createOrExistsDir(new File(destFileDir));
        deleteDir(new File(destFileDir + destFileName));
        FileOutputStream fileOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(new File(destFileDir, destFileName));
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(obj);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (objectOutputStream != null) {
                    objectOutputStream.close();
                    objectOutputStream = null;
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                    fileOutputStream = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 反序列化,從SD卡取出對象
     *
     * @param destFileDir  SD卡目標路徑
     * @param destFileName SD卡文件名
     */
    public static Object readObjectFromSDCard(String destFileDir, String destFileName) {
        FileInputStream fileInputStream = null;

        Object object = null;
        ObjectInputStream objectInputStream = null;

        try {
            fileInputStream = new FileInputStream(new File(destFileDir, destFileName));
            objectInputStream = new ObjectInputStream(fileInputStream);
            object = objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (objectInputStream != null) {
                    objectInputStream.close();
                    objectInputStream = null;
                }
                if (fileInputStream != null) {
                    fileInputStream.close();
                    fileInputStream = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return object;

    }
}

需要權限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

MainActivity 序列寫入

SingletonUtil singletonUtil = SingletonUtil.getInstance();
singletonUtil.setUserId("007");
AndroidUtil.writeObjectToSDCard(singletonUtil, SingletonUtil.ROOT_FILE_DIR, SingletonUtil.USER_STATE_FILE_NAME_DIR);

Process1Activity 反序列化取值

Object object = AndroidUtil.readObjectFromSDCard(SingletonUtil.ROOT_FILE_DIR, SingletonUtil.USER_STATE_FILE_NAME_DIR);
if (object != null) {
    SingletonUtil singletonUtil = (SingletonUtil) object;
    Log.d("wxl", "userId=" + singletonUtil.getUserId());//打印:userId=007
}

AIDL

AIDL,Android 接口定義語言,定義客戶端與服務端進程間通信,服務端有處理多線程時,才有必要使用 AIDL,不然可以使用 Messenger ,後文介紹。

單個應用,多個進程

服務端

AIDL 傳遞數據有基本類型 int,long,boolean,float,double,也支持 String,CharSequence,List,Map,傳遞對象需要實現 Parcelable 接口,這時需要指定 in(客戶端數據對象流向服務端)、out (數據對象由服務端流向客戶端)。

1、Userbean.java

public class UserBean implements Parcelable {
    private int userId;
    private String userName;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public UserBean() {

    }

    private UserBean(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
    }

    /**
     * @return 0 或 1 ,1 含有文件描述符
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 系列化
     *
     * @param dest  當前對象
     * @param flags 0 或 1,1 代表當前對象需要作為返回值,不能立即釋放資源
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(userId);
        dest.writeString(userName);
    }

    /**
     * 反序列化
     */
    public static final Creator<UserBean> CREATOR = new Creator<UserBean>() {
        @Override
        public UserBean createFromParcel(Parcel in) {
            return new UserBean(in);
        }

        @Override
        public UserBean[] newArray(int size) {
            return new UserBean[size];
        }
    };

}

2、UserBean.aidl

Userbean.java 同包下創建對應的 UserBean.aidl 文件,與 aidl 調用和交互。

// UserBean.aidl
package com.wuxiaolong.androidprocesssample;

parcelable UserBean;

3、IUserManager.aidl

// IUserManager.aidl
package com.wuxiaolong.androidprocesssample;

// Declare any non-default types here with import statements
//手動導入
import com.wuxiaolong.androidprocesssample.UserBean;

interface IUserManager {

    //基本數據類型:int,long,boolean,float,double,String
    void hello(String aString);

    //非基本數據類型,傳遞對象
    void getUser(in UserBean userBean);//in 客戶端->服務端


}

4、服務類

新建 AIDLService 繼承 Service,並且實現 onBind() 方法返回一個你實現生成的 Stub 類,把它暴露給客戶端。Stub 定義了一些輔助的方法,最顯著的就是 asInterface(),它是用來接收一個 IBinder,並且返回一個 Stub 接口的實例 。

public class AIDLService extends Service {

    private Binder binder = new IUserManager.Stub() {

        @Override
        public void getUser(UserBean userBean) throws RemoteException {
            Log.d("wxl", userBean.getUserId() + "," + userBean.getUserName() + " from AIDL Service");
        }

        @Override
        public void hello(String aString) throws RemoteException {
            Log.d("wxl", aString + " from AIDL Service");
        }
    };

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

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

AndroidManifest 註冊:

<service
    android:name=".AIDLService"
    android:process=":aidlRemote" />

以上創建完畢,build clean 下,會自動生成 aidl 對應的 java 類供客戶端調用。

客戶端

1、app/build.gradle

需要指定 aidl 路徑:

android {
    //……
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }
}

2、啟動服務,建立聯系

public class MainActivity extends AppCompatActivity {

    private ServiceConnection aidlServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IUserManager remoteService = IUserManager.Stub.asInterface(service);
            UserBean userBean = new UserBean();
            userBean.setUserId(1);
            userBean.setUserName("WuXiaolong");
            try {
                remoteService.getUser(userBean);
                remoteService.hello("Hello");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, AIDLService.class);
        bindService(intent, aidlServiceConnection, Context.BIND_AUTO_CREATE);
    }

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

打印:

com.wuxiaolong.androidprocesssample:aidlRemote D/wxl: 1,WuXiaolong from AIDL Service
com.wuxiaolong.androidprocesssample:aidlRemote D/wxl: Hello from AIDL Service

多個應用,多進程

和上面基本差不多,把服務端和客戶端分別創建的兩個項目,可以互相通信,註意點:

1、服務端創建好的 aidl 文件,帶包拷貝到客戶端項目中;

2、客戶端啟動服務是隱式啟動,Android 5.0 中對 service 隱式啟動有限制,必須通過設置 action 和 package,代碼如下:

AndroidManifest 註冊:

<service android:name=".AIDLService">
    <intent-filter>
        <action android:name="android.intent.action.AIDLService" />
    </intent-filter>

啟動服務:

Intent intent = new Intent();
intent.setAction("android.intent.action.AIDLService");
intent.setPackage("com.wuxiaolong.aidlservice");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

使用 Messenger

Messenger 可以在不同的進程傳遞 Message 對象,而我們可以在 Message 對象中放入我們所需要的數據,這樣就能實現進程間通信了。Messenger 底層實現是 AIDL,對 AIDL 做了封裝, 不需要處理多線程,實現步驟也分為服務端和客戶端,代碼如下:

服務端

MessengerService:

public class MessengerService extends Service {

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


    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MainActivity.MSG_FROM_CLIENT:
                    //2、服務端接送消息
                    Log.d("wxl", "msg=" + msg.getData().getString("msg"));

                    //4、服務端回復消息給客戶端
                    Messenger serviceMessenger = msg.replyTo;
                    Message replyMessage = Message.obtain(null, MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString("msg", "Hello from service.");
                    replyMessage.setData(bundle);
                    try {
                        serviceMessenger.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
            super.handleMessage(msg);
        }
    }

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

AndroidManafest.xml 註冊:

<service
    android:name=".MessengerService"
    android:process=":messengerRemote" />

客戶端

MainActivity

public class MainActivity extends AppCompatActivity {
    public static final int MSG_FROM_CLIENT = 1000;
    public static final int MSG_FROM_SERVICE = 1001;
    private Messenger clientMessenger;
    private ServiceConnection messengerServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //1、發送消息給服務端
            clientMessenger = new Messenger(service);
            Message message = Message.obtain(null, MSG_FROM_CLIENT);
            Bundle bundle = new Bundle();
            bundle.putString("msg", "Hello from client.");
            message.setData(bundle);
            //3、這句是服務端回復客戶端使用
            message.replyTo = getReplyMessenger;
            try {
                clientMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

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

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MainActivity.MSG_FROM_SERVICE:
                    //5、服務端回復消息給客戶端,客戶端接送消息
                    Log.d("wxl", "msg=" + msg.getData().getString("msg"));
                    break;
            }
            super.handleMessage(msg);
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // Messenger 進行通信
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, messengerServiceConnection, Context.BIND_AUTO_CREATE);

    }

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

}

打印信息:

com.wuxiaolong.androidprocesssample:remote D/wxl: msg=Hello from client.
com.wuxiaolong.androidprocesssample D/wxl: msg=Hello from service.

最後

《Android開發藝術探索》一書關於 Android 進程間通信這塊,還有 ContentProvider、Socket 方式,由於篇幅所限,這裏不一一介紹了,有興趣可以自行查看。如果需要這次 Sample 的源碼,可在我的公眾號「吳小龍同學」回復:「AndroidProcessSample」獲取。

參考

《Android開發藝術探索》
Android 中的多進程,你值得了解的一些知識
Android使用AIDL實現跨進程通訊(IPC)

技術分享圖片

Android 進程間通信