AIDL中RemoteCallbackList的使用及權(quán)限驗(yàn)證方式

業(yè)務(wù)場(chǎng)景:
現(xiàn)在要實(shí)現(xiàn)每新增一個(gè)員工,就通知相應(yīng)的部門人員
1蔚鸥、提供一個(gè)AIDL接口庞瘸,由于AIDL中無(wú)法使用普通接口捧弃,所以提供一個(gè)AIDL接口

// IOnNewPersonArrivedListener.aidl
package com.wuc.aidltest;

// Declare any non-default types here with import statements
import com.wuc.aidltest.Person;
// 當(dāng)服務(wù)端有新人加入時(shí),就通知每一個(gè)已經(jīng)申請(qǐng)?zhí)嵝压δ艿挠脩簦捎贏IDL中無(wú)法使用普通接口违霞,所以提供一個(gè)AIDL接口
interface IOnNewPersonArrivedListener {
    void onNewPersonArrived(in Person person);
}

2嘴办、 修改ICalculateInterface代碼,新增注冊(cè)與注銷監(jiān)聽(tīng)器代碼

// ICalculateInterface.aidl
package com.wuc.aidltest;

// Declare any non-default types here with import statements
import com.wuc.aidltest.Person;
import com.wuc.aidltest.IOnNewPersonArrivedListener;
interface ICalculateInterface {
     //計(jì)算兩個(gè)數(shù)的和
     int addNum(int num1,int num2);
     //除了基本數(shù)據(jù)類型买鸽,其他類型的參數(shù)都需要標(biāo)上方向類型:in(輸入), out(輸出), inout(輸入輸出)  
     List<Person> addPerson(in Person person);
     void registerListener(IOnNewPersonArrivedListener listener);
     void unregisterListener(IOnNewPersonArrivedListener listener);
}

IRemoteService代碼

package com.wuc.aidltest;
public class IRemoteService extends Service {
    private static final String TAG = "IRemoteService";
    //存儲(chǔ)注冊(cè)監(jiān)聽(tīng)客戶端集合
    private final CopyOnWriteArrayList <IOnNewPersonArrivedListener> mListenerList = new CopyOnWriteArrayList <>();
    /**
     * CopyOnWriteArrayList支持并發(fā)讀寫涧郊,AIDL方法是在服務(wù)端的Binder線程池中執(zhí)行的,因此當(dāng)多個(gè)客戶端同時(shí)連接的時(shí)候眼五,
     * 會(huì)存在多個(gè)線程同時(shí)訪問(wèn)的情形妆艘,所以我們要在AIDL方法中處理線程同步,這里使用CopyOnWriteArrayList來(lái)進(jìn)行自動(dòng)的線程同步
     * <p>
     * 因?yàn)锳IDL中所支持的是抽象的List看幼,二List只是一個(gè)接口双仍,因此雖然服務(wù)端返回的是CopyOnWriteArrayList,但是在Binder中
     * 會(huì)按照List的規(guī)范去訪問(wèn)數(shù)據(jù)并最終形成一個(gè)新的ArrayList傳遞給客戶端桌吃,所以采用CopyOnWriteArrayList是可以的朱沃,類似的
     * 還有ConcurrentHashMap
     */
    private CopyOnWriteArrayList<Person> mPersonList;
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    private IBinder mIBinder = new ICalculateInterface.Stub() {

        @Override
        public int addNum(int num1, int num2) throws RemoteException {
            return num1 + num2;
        }

        @Override
        public List<Person> addPerson(Person person) throws RemoteException {
            mPersonList.add(person);
            return mPersonList;
        }

        @SuppressLint("NewApi")
        @Override
        public void registerListener(IOnNewPersonArrivedListener listener) throws RemoteException {
           if (!mListenerList.contains(listener)) {
                mListenerList.add(listener);//添加監(jiān)聽(tīng)
            } else {
                Log.d(TAG, "already exists.");
            }
            Log.d(TAG, "registerListener,size:" + mListenerList.size());
        }

        @SuppressLint("NewApi")
        @Override
        public void unregisterListener(IOnNewPersonArrivedListener listener) throws RemoteException {
           if (mListenerList.contains(listener)) {
                mListenerList.remove(listener);//移除監(jiān)聽(tīng)
                Log.d(TAG, "unregister listener succeed.");
            } else {
                Log.d(TAG, "not found,can not unregister.");
            }
            Log.d(TAG, "unregisterListener茅诱,current size:" + mListenerList.size());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mPersonList = new CopyOnWriteArrayList<>();
        return mIBinder;
    }

    private void onNewPersonArrived(Person person) throws RemoteException {
        mPersonList.add(person);
        Log.d(TAG, "onNewPersonArrived, notify listener:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewPersonArrivedListener listener = mListenerList.get(i);
            Log.d(TAG, "onNewPersonArrived, notify listener:" + listener);
            listener.onNewPersonArrived(person);
        }
    }

    /**
     * 每個(gè)5秒增加一個(gè)新人逗物,并通知所有感興趣的員工
     */
    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Person person = new Person("new person name:" + 2, 22);
                try {
                    onNewPersonArrived(person);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

MainActivity代碼

package com.wuc.aidlclient;
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final int MESSAGE_NEW_PERSON_ARRIVED = 1;

    private AppCompatEditText mEdt_num1;
    private AppCompatEditText mEdt_num2;
    private AppCompatButton mBtn_calculate;
    private AppCompatTextView mTxt_result;


    private ICalculateInterface mICalculateInterface;
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (mICalculateInterface == null) {
                return;
            }
            //移除之前綁定的代理并重新綁定遠(yuǎn)程服務(wù)
            mICalculateInterface.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mICalculateInterface = null;
            bindService();
        }
    };
    private ClientHandler mHandler = new ClientHandler();
    /**
     * 當(dāng)客戶端發(fā)起遠(yuǎn)程請(qǐng)求時(shí),由于當(dāng)前線程會(huì)被掛起直至服務(wù)端進(jìn)程返回?cái)?shù)據(jù)瑟俭,所以如果一個(gè)遠(yuǎn)程方法是很耗時(shí)的翎卓,
     * 則不能在UI 線程中發(fā)起此遠(yuǎn)程請(qǐng)求,為了避免阻塞UI 線程出現(xiàn)ANR
     * 由于服務(wù)端的Binder 方法運(yùn)行在 Binder 的線程池中摆寄,所以 Binder 方法不管是否耗時(shí)都應(yīng)該采用同步的方式去實(shí)現(xiàn)失暴,
     * 因?yàn)樗呀?jīng)運(yùn)行在一個(gè)線程中了
     */
    private IOnNewPersonArrivedListener mOnNewPersonArrivedListener = new IOnNewPersonArrivedListener.Stub() {
        @Override
        public void onNewPersonArrived(Person person) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_PERSON_ARRIVED, person).sendToTarget();
        }
    };
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //判斷Binder是否死忙
            //boolean binderAlive = service.isBinderAlive();
            //用于將服務(wù)端的Binder對(duì)象轉(zhuǎn)換為客戶端需要的AIDL接口類型的對(duì)象
            mICalculateInterface = ICalculateInterface.Stub.asInterface(service);
            try {
                mICalculateInterface.registerListener(mOnNewPersonArrivedListener);
                //給binder設(shè)置死忙代理,當(dāng)Binder死忙時(shí)就可以收到通知
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //連接斷開(kāi)微饥,釋放AIDL Binder對(duì)象
            mICalculateInterface = null;
            Log.d(TAG, "binder died");
        }
    };

    @SuppressLint("CutPasteId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEdt_num1 = findViewById(R.id.edt_num1);
        mEdt_num2 = findViewById(R.id.edt_num2);
        mTxt_result = findViewById(R.id.txt_result);
        mBtn_calculate = findViewById(R.id.btn_calculate);

        mBtn_calculate.setOnClickListener(new View.OnClickListener() {
            @SuppressLint("SetTextI18n")
            @Override
            public void onClick(View v) {
                int num1 = Integer.parseInt(mEdt_num1.getText().toString());
                int num2 = Integer.parseInt(mEdt_num2.getText().toString());
                try {
                    int num = mICalculateInterface.addNum(num1, num2);
                    mTxt_result.setText("結(jié)果:" + num);
                } catch (RemoteException e) {
                    e.printStackTrace();
                    mTxt_result.setText("計(jì)算錯(cuò)誤");
                }
                try {
                    List<Person> personList = mICalculateInterface.addPerson(new Person("wuc", 22));
                    Log.d("aidl", personList.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        bindService();
    }

    private void bindService() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.wuc.aidltest",
                "com.wuc.aidltest.IRemoteService"));
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //如果連接持續(xù)逗扒,并且Binder未死亡
        if (mICalculateInterface != null && mICalculateInterface.asBinder().isBinderAlive()) {
            try {
                Log.d(TAG, "unregister listener : " + mOnNewPersonArrivedListener);
                mICalculateInterface.unregisterListener(mOnNewPersonArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(conn);
        super.onDestroy();
    }

    /**
     * 防止Handler泄漏
     */
    private static class ClientHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_PERSON_ARRIVED:
                    Log.d(TAG, "receive new person : " + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }
}

Log日志

服務(wù)端(IRemoteService)
service_log.png

客戶端
client_log.png

從日志中可以看出,在解注冊(cè)的過(guò)程中欠橘,服務(wù)端找不到之前注冊(cè)的listener矩肩;
原因
Binder 會(huì)把客戶端傳遞過(guò)來(lái)的對(duì)象重新轉(zhuǎn)化并生成一個(gè)新的對(duì)象,雖然我們?cè)谧?cè)和解注冊(cè)過(guò)程中使用的是同一個(gè)客戶端肃续,但是通過(guò) Binder 傳遞到服務(wù)端后黍檩,卻會(huì)產(chǎn)生兩個(gè)全新的對(duì)象。而對(duì)象是不能跨進(jìn)程傳輸?shù)氖济瑢?duì)象的跨進(jìn)程傳輸本質(zhì)上都是反序列化的過(guò)程刽酱,這就是為什么 AIDL 中的自定義對(duì)象都必須要實(shí)現(xiàn) Parcelable 接口的原因
解決辦法
用RemoteCallbackList,RemoteCallbackList 是系統(tǒng)專門提供的用于刪除跨進(jìn)程 listener 的類瞧捌,RemoteCallbackList 是一個(gè)泛型棵里,支持管理任意的 AIDL 接口,從它的聲明可以看出,因?yàn)樗械?AIDL 接口都繼承自 IInteface 接口

修改后的IRemoteService

package com.wuc.aidltest;
public class IRemoteService extends Service {
    private static final String TAG = "IRemoteService";
    //存儲(chǔ)注冊(cè)監(jiān)聽(tīng)客戶端集合
    private final RemoteCallbackList<IOnNewPersonArrivedListener> mListenerList = new RemoteCallbackList<>();
    /**
     * CopyOnWriteArrayList支持并發(fā)讀寫衍慎,AIDL方法是在服務(wù)端的Binder線程池中執(zhí)行的转唉,因此當(dāng)多個(gè)客戶端同時(shí)連接的時(shí)候,
     * 會(huì)存在多個(gè)線程同時(shí)訪問(wèn)的情形稳捆,所以我們要在AIDL方法中處理線程同步赠法,這里使用CopyOnWriteArrayList來(lái)進(jìn)行自動(dòng)的線程同步
     * <p>
     * 因?yàn)锳IDL中所支持的是抽象的List,二List只是一個(gè)接口乔夯,因此雖然服務(wù)端返回的是CopyOnWriteArrayList砖织,但是在Binder中
     * 會(huì)按照List的規(guī)范去訪問(wèn)數(shù)據(jù)并最終形成一個(gè)新的ArrayList傳遞給客戶端,所以采用CopyOnWriteArrayList是可以的末荐,類似的
     * 還有ConcurrentHashMap
     */
    private CopyOnWriteArrayList<Person> mPersonList;
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    private IBinder mIBinder = new ICalculateInterface.Stub() {

        @Override
        public int addNum(int num1, int num2) throws RemoteException {
            return num1 + num2;
        }

        @Override
        public List<Person> addPerson(Person person) throws RemoteException {
            mPersonList.add(person);
            return mPersonList;
        }

        @SuppressLint("NewApi")
        @Override
        public void registerListener(IOnNewPersonArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
           /* if (!mListenerList.contains(listener)) {
                mListenerList.add(listener);
            } else {
                Log.d(TAG, "already exists.");
            }*/
            Log.d(TAG, "registerListener侧纯,size:" + mListenerList.getRegisteredCallbackCount());
        }

        @SuppressLint("NewApi")
        @Override
        public void unregisterListener(IOnNewPersonArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
           /* if (mListenerList.contains(listener)) {
                mListenerList.remove(listener);
                Log.d(TAG, "unregister listener succeed.");
            } else {
                Log.d(TAG, "not found,can not unregister.");
            }*/
            Log.d(TAG, "unregisterListener,current size:" + mListenerList.getRegisteredCallbackCount());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mPersonList = new CopyOnWriteArrayList<>();
        int check = checkCallingOrSelfPermission("com.wuc.aidlservice.permission.ACCESS_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mIBinder;
    }

    private void onNewPersonArrived(Person person) throws RemoteException {
        mPersonList.add(person);
        /*Log.d(TAG, "onNewPersonArrived, notify listener:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewPersonArrivedListener listener = mListenerList.get(i);
            Log.d(TAG, "onNewPersonArrived, notify listener:" + listener);
            listener.onNewPersonArrived(person);
        }*/
        synchronized (mListenerList) {
            int n = mListenerList.beginBroadcast();
            try {
                for (int i = 0; i < n; i++) {
                    IOnNewPersonArrivedListener listener = mListenerList.getBroadcastItem(i);
                    if (listener != null) {
                        listener.onNewPersonArrived(person);
                    }
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mListenerList.finishBroadcast();
        }
    }

    /**
     * 每個(gè)5秒增加一個(gè)新人甲脏,并通知所有感興趣的員工
     */
    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Person person = new Person("new person name:" + 2, 22);
                try {
                    onNewPersonArrived(person);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注: RemoteCallbackList 并不是一個(gè)List 眶熬,不能像 List 一樣去操作它,遍歷RemoteCallbackList 必須要以下面的方式進(jìn)行块请,其中 beginBroadcast 和 finishBroadcast 必須配套使用娜氏,哪怕我們僅僅是想要獲取 RemoteCallbackList 中的元素個(gè)數(shù)

 int n = mListenerList.beginBroadcast();
 try {
     for (int i = 0; i < n; i++) {
           IOnNewPersonArrivedListener listener = mListenerList.getBroadcastItem(i);
           if (listener != null) {
                listener.onNewPersonArrived(person);
           }
    }
 } catch (RemoteException e) {
        e.printStackTrace();
 }
 mListenerList.finishBroadcast();

注:
客戶端遠(yuǎn)程調(diào)用服務(wù)端的方法,被調(diào)用的方法運(yùn)行在服務(wù)端的 Binder 線程池中墩新,同時(shí)客戶端線程會(huì)被掛起贸弥,此時(shí)如果服務(wù)端方法執(zhí)行比較耗時(shí),則會(huì)導(dǎo)致客戶端線程長(zhǎng)時(shí)間阻塞在這里海渊,如果此時(shí)客戶端線程是 UI 線程绵疲,則會(huì)導(dǎo)致客戶端ANR ,因此如果我們明確知道某個(gè)遠(yuǎn)程方法是耗時(shí)的臣疑,則要避免在客戶端的 UI 線程中去訪問(wèn)遠(yuǎn)程方法盔憨。
由于客戶端的 onServiceConnected 和 onServiceDisconnected 方法運(yùn)行在 UI線程中,故也不可以在他們里面直接調(diào)用服務(wù)端的耗時(shí)方法朝捆。
服務(wù)端方法本身就運(yùn)行在服務(wù)端的Binder 線程池中般渡,故服務(wù)端的方法本身就可以進(jìn)行大量的耗時(shí)操作,此時(shí)切記不要在服務(wù)端開(kāi)線程去進(jìn)行異步任務(wù)芙盘,除非你明確知道自己在干什么,否則不建議這么做

同理脸秽,當(dāng)遠(yuǎn)程服務(wù)端需要調(diào)用客戶端的 listener 中的方法時(shí)儒老,被調(diào)用的方法運(yùn)行在客戶端的 Binder 池中,故我們同樣不可以在服務(wù)端調(diào)用客戶端耗時(shí)方法记餐,比如針對(duì) IRemoteService 的 onNewPersonArrived 方法驮樊,在它內(nèi)部調(diào)用了客戶端的 IOnNewPersonArrivedListener 中的 onNewPersonArrived 方法,如果客戶端的這個(gè) onNewPersonArrived 方法比較耗時(shí)的話,確保 IRemoteService 的onNewPersonArrived 運(yùn)行在非 UI 線程中囚衔,否則將導(dǎo)致服務(wù)端無(wú)法響應(yīng)

同時(shí)由于客戶端的 IOnNewBookArrivedListener 中的 onNewBookArrived 方法運(yùn)行在客戶端的 Binder 池中挖腰,故不能在里面訪問(wèn)UI相關(guān)的內(nèi)容,如要訪問(wèn)练湿,請(qǐng)用Handler 切換到主線程

Binder是可能意外死亡的猴仑,這往往是由于服務(wù)端進(jìn)程意外停止導(dǎo)致的,此時(shí)我們需要重新連接服務(wù)肥哎。
1辽俗、給Binder 設(shè)置DeathRecipient 監(jiān)聽(tīng),當(dāng)Binder 死亡時(shí)篡诽,我們會(huì)收到 binderDied 方法的回調(diào)崖飘,在 binderDied 方法中我們可以重新綁定遠(yuǎn)程服務(wù)
2、在onServiceDisconnected 中重連遠(yuǎn)程服務(wù)
這兩種方法的區(qū)別在于:onServiceDisconnected 在客戶端的 UI 線程中被回調(diào)杈女,而 binderDied 在客戶端的Binder 線程池中被回調(diào)朱浴,即在binderDied 方法中我們不能訪問(wèn) UI

如何在 AIDL 中使用權(quán)限驗(yàn)證功能?

  1. 在onBind 中進(jìn)行驗(yàn)證达椰,驗(yàn)證不通過(guò)直接返回null 翰蠢,這樣驗(yàn)證失敗的客戶端直接無(wú)法綁定服務(wù),至于驗(yàn)證方式有很多種砰碴,比如使用permission 驗(yàn)證躏筏,使用這種驗(yàn)證方式,我們需要先在 AndroidMenifest 中聲明所需的權(quán)限呈枉,比如:
<!--定義權(quán)限-->
    <permission
        android:name="com.wuc.aidlservice.permission.ACCESS_SERVICE"
        android:protectionLevel="normal"/>

定義權(quán)限后趁尼,在IRemoteService的onBinder中做權(quán)限驗(yàn)證

 @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.wuc.aidlservice.permission.ACCESS_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mIBinder;
    }

一個(gè)應(yīng)用來(lái)綁定我們的服務(wù)時(shí),會(huì)驗(yàn)證這個(gè)應(yīng)用的權(quán)限猖辫,如果他沒(méi)有使用這個(gè)權(quán)限酥泞,則onBind 方法就會(huì)直接返回 null,最終這個(gè)應(yīng)用無(wú)法綁定到我們的服務(wù)啃憎,這樣就達(dá)到了權(quán)限驗(yàn)證的效果芝囤,這種方法同樣適用于 Messenger中。
如果我們自己內(nèi)部的應(yīng)用想要綁定到我們的服務(wù)中辛萍,只需在它的 AndroidMenifest 文件中采用如下方式使用 permission 即可

<uses-permission android:name="com.wuc.aidlservice.permission.ACCESS_SERVICE"/>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悯姊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贩毕,更是在濱河造成了極大的恐慌悯许,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辉阶,死亡現(xiàn)場(chǎng)離奇詭異先壕,居然都是意外死亡瘩扼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門垃僚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)集绰,“玉大人,你說(shuō)我怎么就攤上這事谆棺≡匝啵” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵包券,是天一觀的道長(zhǎng)纫谅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)溅固,這世上最難降的妖魔是什么付秕? 我笑而不...
    開(kāi)封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮侍郭,結(jié)果婚禮上询吴,老公的妹妹穿的比我還像新娘。我一直安慰自己亮元,他們只是感情好猛计,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著爆捞,像睡著了一般奉瘤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上煮甥,一...
    開(kāi)封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天盗温,我揣著相機(jī)與錄音,去河邊找鬼成肘。 笑死卖局,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的双霍。 我是一名探鬼主播砚偶,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼遣蚀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼启妹!你這毒婦竟也來(lái)了碟婆?” 一聲冷哼從身側(cè)響起红伦,我...
    開(kāi)封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赃阀,沒(méi)想到半個(gè)月后量愧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蔓纠,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸣个,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年羞反,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囤萤。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昼窗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涛舍,到底是詐尸還是另有隱情澄惊,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布富雅,位于F島的核電站掸驱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏没佑。R本人自食惡果不足惜毕贼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛤奢。 院中可真熱鬧鬼癣,春花似錦、人聲如沸啤贩。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)痹屹。三九已至章郁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間志衍,已是汗流浹背暖庄。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留足画,地道東北人雄驹。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像淹辞,于是被迫代替她去往敵國(guó)和親医舆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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

  • 一象缀、IPC簡(jiǎn)介 (1)IPC是Inter-Process Communication的縮寫蔬将,含義為進(jìn)程間通信或者跨...
    遙遙的遠(yuǎn)方閱讀 7,224評(píng)論 0 3
  • Android開(kāi)發(fā)藝術(shù)探索 第二章IPC機(jī)制 Linux中IPC通信方式?答:命名管道央星,共享內(nèi)存霞怀,信號(hào)量(具體再細(xì)...
    方木Rudy閱讀 1,097評(píng)論 0 2
  • 1.使用Bundle ----> 用于android四大組件間的進(jìn)程間通信 android的四大組件都可使用B...
    小帝Ele閱讀 1,559評(píng)論 2 6
  • 你獨(dú)自一人爬在地上畫畫,口里喊著我莉给,我走向你毙石,六六也跟著過(guò)來(lái)廉沮,六六提議幫你畫畫,你說(shuō):好徐矩,有很多筆……不連貫滞时,卻依...
    想木音閱讀 158評(píng)論 0 0
  • 這是以前剛開(kāi)始學(xué)unity時(shí)遇到的一些問(wèn)題的筆記,現(xiàn)在看來(lái)有些幼稚滤灯,但是還是發(fā)出來(lái)給剛?cè)腴T的同學(xué)參考一下坪稽。 1. ...
    JervieQin閱讀 1,352評(píng)論 0 1