什麼鬼!單例竟然失效了,一個地方設置值,另個地方竟然取不到,這怎麼可能?沒道理啊!排查半天,發現這兩就不在一個進程裏,才恍然大悟……java
按照操做系統中的描述:進程通常指一個執行單元,在 PC 和移動設備上指一個程序或者一個應用。android
咱們都知道,系統爲 APP 每一個進程分配的內存是有限的,若是想獲取更多內存分配,可使用多進程,將一些看不見的服務、比較獨立而又至關佔用內存的功能運行在另一個進程當中。bash
先放出最終實踐後的目錄結構,有個大概印象,後面一一介紹。 多線程
AndroidManifest.xml 清單文件中註冊 Activity、Service 等四大組件時,指定 android:process 屬性便可開啓多進程,如:app
<activity
android:name=".Process1Activity"
android:process=":process1" />
<activity
android:name=".Process2Activity"
android:process="com.wuxiaolong.androidprocesssample.process2" />
複製代碼
一、com.wuxiaolong.androidprocesssample
,主進程,默認的是應用包名;ide
二、android:process=":process1"
,「:」開頭,是簡寫,完整進程名包名 + :process1
;gradle
三、android:process="com.wuxiaolong.androidprocesssample.process2"
,以小寫字母開頭的,屬於全局進程,其餘應用能夠經過 ShareUID 進行數據共享;ui
四、進程命名跟包名的命名規範同樣。this
咱們自定義一個 Application 類,onCreate
方法進行打印 Log.d("wxl", "AndroidApplication onCreate");
,而後啓動 Process1Activity:spa
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 卡取出對象,完整代碼以下:
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" />
複製代碼
SingletonUtil singletonUtil = SingletonUtil.getInstance();
singletonUtil.setUserId("007");
AndroidUtil.writeObjectToSDCard(singletonUtil, SingletonUtil.ROOT_FILE_DIR, SingletonUtil.USER_STATE_FILE_NAME_DIR);
複製代碼
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,Android 接口定義語言,定義客戶端與服務端進程間通訊,服務端有處理多線程時,纔有必要使用 AIDL,否則可使用 Messenger ,後文介紹。
AIDL 傳遞數據有基本類型 int,long,boolean,float,double,也支持 String,CharSequence,List,Map,傳遞對象須要實現 Parcelable 接口,這時須要指定 in(客戶端數據對象流向服務端)、out (數據對象由服務端流向客戶端)。
一、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];
}
};
}
複製代碼
二、UserBean.aidl
Userbean.java 同包下建立對應的 UserBean.aidl 文件,與 aidl 調用和交互。
// UserBean.aidl
package com.wuxiaolong.androidprocesssample;
parcelable UserBean;
複製代碼
三、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 客戶端->服務端
}
複製代碼
四、服務類
新建 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 類供客戶端調用。
一、app/build.gradle
須要指定 aidl 路徑:
android {
//……
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
}
複製代碼
二、啓動服務,創建聯繫
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
複製代碼
和上面基本差很少,把服務端和客戶端分別建立的兩個項目,能夠互相通訊,注意點:
一、服務端建立好的 aidl 文件,帶包拷貝到客戶端項目中;
二、客戶端啓動服務是隱式啓動,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 能夠在不一樣的進程傳遞 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:
//二、服務端接送消息
Log.d("wxl", "msg=" + msg.getData().getString("msg"));
//四、服務端回覆消息給客戶端
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) {
//一、發送消息給服務端
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);
//三、這句是服務端回覆客戶端使用
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:
//五、服務端回覆消息給客戶端,客戶端接送消息
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開發藝術探索》