第九章--進(jìn)程和進(jìn)程間的通信

1瘸彤、進(jìn)程相關(guān)的知識(shí)

1.1婚肆、什么叫進(jìn)程

說到進(jìn)程,很多人包括我會(huì)聯(lián)想到線程,以為這兩個(gè)東西之間有什么關(guān)系邻寿,但是這一周的學(xué)習(xí)渤弛,至少讓我知道己肮,多進(jìn)程和單線程主穗,多進(jìn)程和多線程,單進(jìn)程和多線程這樣的組合都可以堤如。借用老師的比喻蒲列,線程比作流水線,進(jìn)程就是一個(gè)車間搀罢,而我們的系統(tǒng)就是工廠蝗岖,然而它們的數(shù)量之間不一定是現(xiàn)實(shí)中的。進(jìn)程是系統(tǒng)進(jìn)行資源分配以及調(diào)度的基本單位榔至,這個(gè)意思就是會(huì)所同一個(gè)進(jìn)程里內(nèi)存資源是共享的抵赢,然而不同進(jìn)程里的資源是不共享的,這就涉及到進(jìn)程間通信的問題唧取,后面會(huì)講到铅鲤。

1.2、創(chuàng)建進(jìn)程

創(chuàng)建一個(gè)進(jìn)程很簡單枫弟,只需要在Androidmenifest中添加process標(biāo)簽邢享,就可以創(chuàng)建一個(gè)進(jìn)程,命名格式一般為包名淡诗,如:

android:process="com.example.w"

如果只是想創(chuàng)建一個(gè)遠(yuǎn)程服務(wù)骇塘,可以用":"創(chuàng)建一個(gè)子進(jìn)程程如:

android:process=":remote"

1.3、進(jìn)程間的等級(jí)

進(jìn)程可有如下等級(jí):

  1. 前臺(tái)進(jìn)程韩容;用戶正在使用的進(jìn)程款违,簡單理解就是用戶正在交互的對象。一直存在的前臺(tái)進(jìn)程是極少的群凶,而且前臺(tái)進(jìn)程一般是最后內(nèi)存小的連前臺(tái)進(jìn)程都沒有地方運(yùn)行的時(shí)候插爹,才會(huì)殺掉進(jìn)程。
  2. 可見進(jìn)程:如彈出一個(gè)對話框的Acitivity座掘,后面的Activity是可見的递惋。后面可見的Activity可以被認(rèn)為是一個(gè)可見進(jìn)程柔滔。
  3. 服務(wù)進(jìn)程:一個(gè)通過startService()啟動(dòng)的service溢陪,用戶雖然看不到然而進(jìn)行著和用戶關(guān)注的任務(wù)萍虽,如下載,播放音樂
  4. 后臺(tái)進(jìn)程:按了home鍵之后不可見的activity形真,一般是執(zhí)行了onStop()方法之后杉编,在內(nèi)存不足的情況下可能會(huì)被回收
  5. 空進(jìn)程:里面沒有任何可以運(yùn)行的程序組件,這些進(jìn)程存在就是作為一個(gè)緩存咆霜,為下一次啟動(dòng)程序縮短時(shí)間邓馒。

參考:google文檔 左邊的翻譯

2、多進(jìn)程之間的通信

2.1蛾坯、多進(jìn)程的優(yōu)點(diǎn)

  • 穩(wěn)定安全:一個(gè)子進(jìn)程如果因?yàn)槟承┊惓=Y(jié)束之后光酣,不會(huì)直接導(dǎo)致主程序的崩潰。
  • 擴(kuò)大內(nèi)存空間:android系統(tǒng)對于一個(gè)應(yīng)用程序的內(nèi)存占用是有限制的脉课,當(dāng)使用多進(jìn)程的時(shí)候救军,可以減少主進(jìn)程占用的內(nèi)存,這樣降低被系統(tǒng)kill的概率倘零。

2.2唱遭、創(chuàng)建多進(jìn)程時(shí)候注意的問題

  • 每個(gè)進(jìn)程初始化的時(shí)候都會(huì)執(zhí)行一遍Application的onCreate()方法,在這個(gè)方法中一般是進(jìn)行一些全局初始化的操作呈驶,所以進(jìn)程初始化的過程多次執(zhí)行onCreate()是浪費(fèi)資源拷泽,解決方法,獲取當(dāng)前的進(jìn)程名袖瞻,只初始化當(dāng)前的進(jìn)程司致,如下代碼:
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //獲取當(dāng)前pid
        int pid = android.os.Process.myPid();

        Log.i("haha", "application pid is :" + pid);

        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        //進(jìn)程列表
        List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();

        if (processInfos != null && !processInfos.isEmpty()){
            for (ActivityManager.RunningAppProcessInfo appProcessInfo : processInfos){
                //匹配當(dāng)前進(jìn)程的pid
                if (appProcessInfo.pid == pid){
                    //匹配進(jìn)程名
                    if (TextUtils.equals(appProcessInfo.processName,getPackageName())){
                        Log.i("haha", "主進(jìn)程被初始化了");

                    }else if (TextUtils.equals(appProcessInfo.processName, "com.text.messenger.service")){
                        Log.i("haha", "其他進(jìn)程被初始化了");
                    }
                }
            }
        }
    }
}

每個(gè)進(jìn)程都有自己的初始化代碼,這樣就不會(huì)被初始化多次了聋迎。

  • 進(jìn)程不是越多越好脂矫,當(dāng)進(jìn)程很多的時(shí)候就要考慮耗電等其他問題

以上參考:進(jìn)程初始化中Application多次初始化的問題

2.3、進(jìn)程間的通信ipc

inter process communication

正因?yàn)檫M(jìn)程間內(nèi)存不共享砌庄,所以就需要方法來進(jìn)行進(jìn)程間數(shù)據(jù)的傳遞羹唠。

(單進(jìn)程,多線程)->handler
(多進(jìn)程娄昆,單線程)->Messenger
(多線程佩微,多進(jìn)程)->AIDL(android interface definition language)

這里推薦一個(gè)最近發(fā)現(xiàn)的可在國內(nèi)用的開發(fā)者網(wǎng)站:android developers , 感謝群里胖大哥。

方法1:Messenger

官方文檔上有介紹萌焰,對于大多數(shù)應(yīng)用來說哺眯,多進(jìn)程單線程的操作已經(jīng)能夠滿足很多需求,而且使用Messenger比使用AIDL簡單扒俯。

文檔里的代碼:

public class MessengerService extends Service {

    static final int MSG_SAY_HELLO = 1;
    
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                //接收到客戶端的消息
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    final Messenger mMessenger = new Messenger(new IncomingHandler());

    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}

首先創(chuàng)建一個(gè)Handler奶卓,用于處理客戶端發(fā)過來的消息一疯,然后創(chuàng)建Messenger對象,這個(gè)對象用于在客戶端如Activity中給Service這個(gè)進(jìn)程發(fā)送消息夺姑,通過onBind()方法返回給客戶端Binder對象墩邀,在客戶端中使用IBinder對象將Messenger實(shí)例化,然后使用這個(gè)Messenger給服務(wù)端發(fā)送消息盏浙。那么客戶端(這里是Activity)代碼如下:(來自文檔)

public class ActivityMessenger extends Activity {
    
    Messenger mService = null;
    //是否連接成功
    boolean mBound;
     
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
        //實(shí)例化Messenger對像
            mService = new Messenger(service);
            mBound = true;
        }

        public void onServiceDisconnected(ComponentName className) {

            mService = null;
            mBound = false;
        }
    };

    public void sayHello(View v) {
        if (!mBound) return;
        
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
        //使用該Messenger發(fā)送消息
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

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

    @Override
    protected void onStart() {
        super.onStart();
        // 綁定服務(wù)
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 解綁服務(wù)
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}

上面的代碼和前面說的流程差不多,得到Messenger對象就可以發(fā)送消息了眉睹。還有還有別忘記在AndroidMenifest中注冊這個(gè)Service,并聲明android:process這個(gè)屬性废膘。

這個(gè)例子只是簡單的客戶端給服務(wù)端發(fā)送消息竹海,顯然功能不能滿足,有來有往才符合交互丐黄。接下來就是服務(wù)端給客戶端發(fā)送消息斋配,文檔里有這樣一段話:

If you want the service to respond, then you need to also create a Messenger in the client. Then when the client receives the onServiceConnected() callback, it sends a Message to the service that includes the client's Messenger in the replyTo parameter of the send() method.

我英語不好開始看的時(shí)候用某度翻譯了一下,也看不懂灌闺,然后它貼心的告訴我有個(gè)例子艰争,我立馬就去看了。最后一句話的意思才懂了一些菩鲜。
首先园细,上面英語告訴我們,得先在客戶端新建一個(gè)Messenger接校,那么問題來了猛频,這個(gè)Messenger是要傳給服務(wù)端來給客戶端發(fā)送消息的,如果有很多的客戶端怎么辦蛛勉?我們可以考慮在服務(wù)端用一個(gè)Map或者List來保存這些不同客戶端的Messenger鹿寻,如:

ArrayList<Messenger> mClients = new ArrayList<>();

服務(wù)端的代碼:

/**
 *
 * 多進(jìn)程,單線程
 * Created by W on 2016/9/4.
 */
public class MessengerService extends Service {

    public static final int MSG_REGISTER_CLIENT = 1;

    public static final int MSG_UNREGISTER_CLIENT = 2;

    public static final int MSG_SET_VALUE = 3;

    int mValue = 122;

    ArrayList<Messenger> mClients = new ArrayList<>();
    //接收傳過來的消息
    class InComingHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what){
                case MSG_REGISTER_CLIENT:
                    Log.i("haha", "添加了");
                    mClients.add(msg.replyTo);
                    break;
                case MSG_UNREGISTER_CLIENT:
                    mClients.remove(msg.replyTo);
                    Log.i("haha", "移除");
                    break;
                case MSG_SET_VALUE:
                    try {
                        Log.i("haha", "發(fā)送給Activity");
                        mClients.get(0).send(Message.obtain(null, MSG_SET_VALUE, mValue,0));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        mClients.remove(0);
                    }
                    break;
                default:
                    super.handleMessage(msg);
             }
        }
    }

    Messenger mMessenger = new Messenger(new InComingHandler());

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

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

然后客戶端的代碼:

/**
 *
 * Created by W on 2016/9/4.
 */
public class MessengerActivity extends AppCompatActivity{

    boolean mBound = false;
    private Messenger mMessenger;

    //該Activity的Messenger
    Messenger messenger = new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MessengerService.MSG_SET_VALUE:
                    Log.i("haha", "從服務(wù)傳過來的:" + msg.arg1 + "");
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    });

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger =  new Messenger(service);
            mBound = true;
            Log.i("haha", "服務(wù)連接成功");

            //以下的代碼诽凌,就是一旦連接成功毡熏,以下的代碼,就是一旦連接成功侣诵,就把該activity的Messenger傳給服務(wù)端
            Message message = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT);
            message.replyTo = messenger;
            try {
                mMessenger.send(message);
                Log.i("haha", "給服務(wù)發(fā)消息成功");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mMessenger = null;
            mBound = true;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.messenger);
        Log.i("haha", "onCreate()");

        Button button = (Button) findViewById(R.id.send_button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = Message.obtain(null, MessengerService.MSG_SET_VALUE);
                message.replyTo = messenger;
                try {
                    mMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

            }
        });

    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.i("haha", "開始綁定服務(wù)");
        bindService(new Intent(this, MessengerService.class), mServiceConnection, Context.BIND_AUTO_CREATE);

    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound){
            if (mMessenger != null){
                Message message = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT);
                message.replyTo = messenger;
                Log.i("haha", "給服務(wù)發(fā)送消息解除監(jiān)聽");
                try {
                    mMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i("haha", "onDestroy");
    }
}

Activity創(chuàng)建痢法,走onCreate(),onStart()杜顺,在start中綁定服務(wù)财搁,然后回調(diào)的接口返回了Service的Messenger對象,Activity也把自己的Messenger對象通過msg.replyTo傳給了服務(wù)端躬络,同時(shí)mClients添加這個(gè)Messenger尖奔。這樣之后它們就可以互相發(fā)消息了。可能我講的不是很好提茁,如果在真機(jī)上調(diào)試看Log淹禾,就基本上能理清這個(gè)流程。

哎呀不用不知道茴扁,Messenger有一個(gè)特別坑的地方铃岔,發(fā)現(xiàn)不能傳一個(gè)序列啊,這周作業(yè)本來是打算把一個(gè)對象傳過去丹弱,然而不行啊德撬。還有一個(gè)在進(jìn)程間全局變量沒什么作用啊铲咨。果然只有實(shí)踐才知道好多東西

方法2.AIDL

我感覺沒用這個(gè)aidl都已經(jīng)忘的差不多了躲胳。
首先在android studio中main目錄下新建一個(gè)AIDL文件夾,如圖:


這里寫圖片描述

基本上是一路下一步纤勒。我們看到生成的文件里面有一個(gè).aidl的文件:

// IMyAidlInterface.aidl
package com.example.myactionbardemo;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    String getName(String aName);
}

這里生成了一個(gè)接口坯苹,里面的抽象方法是需要實(shí)現(xiàn)的。
然后點(diǎn)任務(wù)欄上的tools->make project摇天。就會(huì)在如下菜單下生成一個(gè)接口:


這里寫圖片描述

重點(diǎn)關(guān)注有個(gè)Stub類實(shí)現(xiàn)了IMyAidlInterface接口粹湃,然后有個(gè)asInterface()方法可以生成接口對象。這就是我們做的前期工作泉坐,還有如果重新修改了.aidl文件一定要重新make project为鳄。
然后就是開始使用了,定義一個(gè)服務(wù)端:

public class AIDLService extends Service{

    IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() {
        //需要實(shí)現(xiàn)的兩個(gè)方法腕让,或者說會(huì)回調(diào)的方法孤钦。
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public String getName(String aName) throws RemoteException {
            return aName + "style";
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
    //返回的Stub對象
        return mStub;
    }
}

這個(gè)就會(huì)傳到客戶端,這里仍然用Activity代替客戶端:

    IMyAidlInterface mIMyAidlInterface;

    ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

以上的代碼應(yīng)該是比較熟悉了纯丸,得到了接口對象偏形,就能回調(diào)她的方法,相當(dāng)于把客戶端的數(shù)據(jù)傳給服務(wù)端進(jìn)行操作了觉鼻,最重要的事它可以傳對象啊俊扭,這點(diǎn)就足以了。

另坠陈,進(jìn)程間傳遞的對象需要序列化萨惑,在此android studio有個(gè)parcelable插件可以自動(dòng)給一個(gè)對象序列化。

再另仇矾,messenger底層也是用aidl實(shí)現(xiàn)的庸蔼。

寫的有點(diǎn)多,下面的英語看看就好若未。

If you only need to interact with the service while your activity is visible, you should bind during onStart() and unbind during onStop().
If you want your activity to receive responses even while it is stopped in the background, then you can bind during onCreate() and unbind during onDestroy(). Beware that this implies that your activity needs to use the service the entire time it's running (even in the background), so if the service is in another process, then you increase the weight of the process and it becomes more likely that the system will kill it.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朱嘴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萍嬉,老刑警劉巖乌昔,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異壤追,居然都是意外死亡磕道,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門行冰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溺蕉,“玉大人,你說我怎么就攤上這事悼做》杼兀” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵肛走,是天一觀的道長漓雅。 經(jīng)常有香客問我,道長朽色,這世上最難降的妖魔是什么邻吞? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮葫男,結(jié)果婚禮上抱冷,老公的妹妹穿的比我還像新娘。我一直安慰自己梢褐,他們只是感情好旺遮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著利职,像睡著了一般趣效。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猪贪,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天跷敬,我揣著相機(jī)與錄音,去河邊找鬼热押。 笑死西傀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桶癣。 我是一名探鬼主播拥褂,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼牙寞!你這毒婦竟也來了饺鹃?” 一聲冷哼從身側(cè)響起莫秆,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悔详,沒想到半個(gè)月后镊屎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茄螃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年缝驳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片归苍。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡用狱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拼弃,到底是詐尸還是另有隱情夏伊,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布肴敛,位于F島的核電站署海,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏医男。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一捻勉、第九天 我趴在偏房一處隱蔽的房頂上張望镀梭。 院中可真熱鬧,春花似錦踱启、人聲如沸报账。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽透罢。三九已至,卻和暖如春冠蒋,著一層夾襖步出監(jiān)牢的瞬間羽圃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工抖剿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朽寞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓斩郎,卻偏偏與公主長得像脑融,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子缩宜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容