SDK封裝AIDL訪問Native Service

版權說明:本文為 開開向前沖 原創文章,轉載請注明出處;
注:限于作者水平有限,文中有不對的地方還請指教

前言:現在是智能電子時代,五花八門的智能電子設備隨處可見,這些電子設備如何實現各自的特色呢?硬件支持,比如智能手機,手表;那么問題來了,我們的硬件提供的服務就在那里,APP層如何去訪問這些服務呢?第一時間想到JNI-沒問題,正如前面所說,JNI可以訪問native,但是這里將會介紹另外一種實現:AIDL——>Native Service;

實現原理:Native Service實現Binder通信架構,向ServiceManager注冊,向外提供通訊接口,Java層定義AIDL,剩下的事情利用Binder 框架完成。

1 實現Native Service

前一篇transac——>onTransact 文章最后有提到如何實現一個Native Service,Native Service 實現步驟如下:

1.實現一個接口文件,IXXXService,繼承IInterface
2.定義BnXXX,繼承BnInterface<IXXXService>。實現一個XXXService,繼承BnXXX,并實現onTransact()函數。
3.定義BpXXX,繼承BpInterface<IXXXService>。

這里我實現一個HelloWorld的native Service;

1.1 實現IXXXService接口
------> IHelloService.h
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
namespace android
{
    class IHelloService : public IInterface
    {
    public:
        DECLARE_META_INTERFACE(HelloService); //使用宏,申明HelloService
        virtual void HelloWorld()=0; //定義方法
    };

    //定義命令字段
    enum
    {
        HELLO_CMD = IBinder::FIRST_CALL_TRANSACTION,//為0
    };

    //申明客戶端BpMyService
    class BpHelloService: public BpInterface<IHelloService> {
    public:
        BpHelloService(const sp<IBinder>& impl);
        virtual void HelloWorld();
    };

    //申明服務端BnHelloService
    class BnHelloService: public BnInterface<IHelloService> {
    public:
        virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
                uint32_t flags = 0);
        virtual void HelloWorld();
    };
}

文件名為IHelloService.h;1:class IHelloService繼承于IInterface;2:定義了待實現的接口方法HelloWorld();3:使用宏DECLARE_META_INTERFACE(HelloService)聲明HelloService;4:使用enum 聲明命令code HELLO_CMD ;

1.2 定義BnXXX
1.2.1 定義BnXXX, BnXXX定義在IHelloService.h文件中;
    //申明服務端BnHelloService
    class BnHelloService: public BnInterface<IHelloService> {
    public:
        virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
                uint32_t flags = 0);
        virtual void HelloWorld();
    };
1.2.2 實現XXXService

XXXService繼承于BnXXX,也就是實現BnXXX中的方法;onTransact可以放到IHellSerivce.cpp中實現,也可以在這里實現;

------>  HelloService.h =========>定義HelloService,繼承BnHelloService
#include "IHelloService.h"
#include <cutils/log.h>
#include <utils/RefBase.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>

namespace android {
    class HelloService : public BnHelloService
    {
        public:
            static int instantiate();
        private:
            HelloService();
            virtual ~HelloService();    
            virtual void HelloWorld();
    };
};

---------> HelloService.cpp =======>實現HelloService,即實現BnXXX中的方法
#include "HelloService.h"
#include <cutils/log.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>

namespace android {
    HelloService::HelloService() { 
    }
    HelloService::~HelloService() {
    }
    int HelloService::instantiate() {
        int ret = defaultServiceManager()->addService(
                      String16("hello.service"), new HelloService());//向外提供服務
        return ret;
    }

    // 實現服務端HelloWorld方法
    void HelloService::HelloWorld() {
        printf("HelloService::HelloWorld\n");
    }
}
1.2.3 BpXXX的定義和實現實現放到IHelloService.cpp中;

IHelloService.cpp是核心文件,該文件中需要調用IMPLEMENT_META_INTERFACE實現前面的DECLARE_META_INTERFACE,下面列出完整的IHelloService.cpp,IHelloService.cpp實現BpXXX;

-------> IHelloService.cpp
#include "IHelloService.h"
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
namespace android
{
    //定義客戶端BpHelloService
    class BpHelloService: public BpInterface<IHelloService> {
        public:
            BpHelloService(const sp<IBinder>& impl) 
                : BpInterface<IHelloService>(impl) {
            }
            virtual void HelloWorld() {
                Parcel data, reply;
                data.writeInterfaceToken(IHelloService::getInterfaceDescriptor());
                remote()->transact(HELLO_CMD, data, &reply);
                printf("get num from BnHelloService: %d\n", reply.readInt32());
            }
    
    };
    IMPLEMENT_META_INTERFACE(HelloService, "com.keiven.binder.IHelloService");//核心核心,這里的字符串很重要

    //服務端,接收遠程消息,onTransact方法處理Client傳遞過來的消息
    //這里onTransact是屬于BnHelloService,即使將該方法實現
    //放到HelloService中實現也不能寫成 HelloService::onTransact
    status_t BnHelloService::onTransact(uint_t code,      
                                        const Parcel& data, 
                                        Parcel* reply, 
                                        uint32_t flags) {
        CHECK_INTERFACE(IHelloService, data, reply);
        reply->writeNoException();//如果沒有writeNoException(),則應用程序訪問過程會獲得異常
        switch (code) {
        case HELLO_CMD: {    //收到HELLO_CMD命令的處理流程,這個值從Client端傳過來
            printf("HelloService:: got the client helloworld\n");
            CHECK_INTERFACE(IHelloService, data, reply);
            HelloWorld(); //這里HelloWorld()方法會調用前面HelloService.cpp中實現的HelloWorld方法
            reply->writeInt32(2015);
            return NO_ERROR;
            }
            break;
        default:
            break;
        }
        return NO_ERROR;
    }

}

IHelloService.h定義了BpHelloService 和 BnHelloService,IHelloService.cpp實現了BpHelloService , BnHelloService 通過HelloService繼承實現,也可以不繼承直接實現,到這里這個native Service 基本設計完成,還有很重要一點沒實現,我們需要這個native Service 能對Java 提供接口,Java 應用層通過ServiceManager.getService接口獲取系統服務,所以這里我們需要將該native Service注冊到ServiceManager,
defaultServiceManager()->addService(String16("hello.service"), new HelloService());

這里就完成了Native Service的創建,我們將該這個native Service 編譯成一個so庫,由于我們代碼中有依賴其他庫中的內容,比如libbinder,liblog,libcutils等,所以編寫Android.mk編譯腳本時需要將這些庫導入;

2 建立服務端server
------> HelloServer.cpp
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <stdio.h>
#include <HelloService.h>
using namespace android;

int main(void)
{
    printf("Hello server - main() begin\n");
    sp<ProcessState> proc(ProcessState::self());
    int ret = HelloService::instantiate();//注冊HelloService到ServiceManager
    printf("Hello server -Hello Service::Instance return %d\n", ret);
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();

    return 0;
}

將HelloServer.cpp 編譯成一個可執行程序helloserver,然后push 到/system/bin 下執行;

3 封裝一個SDK jar 完成對native Service的調用

IMPLEMENT_META_INTERFACE(HelloService, "com.keiven.binder.IHelloService")中指定HelloService實現接口的NAME="com.keiven.binder.IHelloService";NAME會給I##INTERFACE::descriptor賦值,到這里我們知道我們的AIDL的包名和命名了,AIDL的名字應該叫IHelloService.aidl,包名為"com.keiven.binder",這樣利用AIDL工具生成的JAVA文件就為com.keiven.binder.IHelloService.java,DESCRIPTOR為"com.keiven.binder.IHelloService";

-------> IInterface.h
// ----------------------------------------------------------------------

#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();                                            \


#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
    I##INTERFACE::~I##INTERFACE() { }                                   \


#define CHECK_INTERFACE(interface, data, reply)                         \
    if (!data.checkInterface(this)) { return PERMISSION_DENIED; }       \

// ----------------------------------------------------------------------
// No user-serviceable parts after this...
3.1 封裝一個二進制SDK Jar包用于訪問 native Service

對于Native Service,這里封裝一個SDK去調用,SDK需要實現那些內容呢???

  1. 在Java層實現一個AIDL和native Service 相對應;
  2. 調用ServiceManager.getService("hello.service")獲取HelloService::instantiate()中注冊的服務;
  3. 封裝SDK 為一個二進制jar(目的:核心代碼不對外公開)。

下面是二進制Jar 包的源碼目錄結構,由于會用到ServiceManager,所以這里新建一個包和系統ServiceManager的包名一樣,這個類只有一個方法,getService(String name);這個方法不需要實現,直接返回null就可以;


目錄結構.png
------> IHelloService.aidl
// IHelloService.aidl
package com.keiven.binder;

interface IHelloService {
     void helloWorld();
}

AIDL 生成的文件如下:
static final int TRANSACTION_helloWorld = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

------> IHelloService.java
/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: E:\\Projects\\apk\\TestJar\\app\\src\\main\\aidl\\com\\keiven\\binder\\IHelloService.aidl
 */
package com.keiven.binder;
public interface IHelloService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.keiven.binder.IHelloService {
        private static final java.lang.String DESCRIPTOR = "com.keiven.binder.IHelloService";
        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        /**
         * Cast an IBinder object into an com.keiven.binder.IHelloService interface,
         * generating a proxy if needed.
         */
        public static com.keiven.binder.IHelloService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.keiven.binder.IHelloService))) {
                return ((com.keiven.binder.IHelloService) iin);
            }
            return new com.keiven.binder.IHelloService.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, 
                                  int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_helloWorld: {
                    data.enforceInterface(DESCRIPTOR);
                    this.helloWorld();
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        private static class Proxy implements com.keiven.binder.IHelloService {
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }
            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
            @Override
            public void helloWorld() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_helloWorld, _data, _reply, 0);//java 部分會調用這里
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        static final int TRANSACTION_helloWorld = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
      //TRANSACTION_helloWorld 必須要和HELLO_CMD 的值相等,BnHelloService.cpp中就是根據這個值去調用相應方法;
    }
    public void helloWorld() throws android.os.RemoteException;
}

這里helloWorld()方法調用mRemote.transact(Stub.TRANSACTION_helloWorld, _data, _reply, 0);最終會在BnHelloService::onTransact()方法中的switch(code) { case HELLO_CMD:}中得到處理,所以TRANSACTION_helloWorld 必須要和HELLO_CMD相等;

aidl 很簡單,只定義了一個helloWorld()方法;HelloManager用于獲取前面在HelloServer中注冊的服務,HelloManager中會調用ServiceManager.getService();HelloService 用于對外提供接口;下面是代碼;

------> ServiceManager.java
package android.os;

public class ServiceManager {
    public static IBinder getService(String name) { return null; }//沒有真正實現,
    //這里的類和方法是當做系統ServiceManager的代理
}

------> HelloManager.java
package com.keiven.binder;

import android.os.IBinder;
import android.os.ServiceManager;
import android.util.Log;

public class HelloManager {
    private static String HELLO_SERVICE_NAME = "hello.service";//這個名字是前面注冊時候的名字
    private static IHelloService helloService = null;
    public static IHelloService getHelloServiceStub() {
        IBinder binder = ServiceManager.getService(HELLO_SERVICE_NAME);//獲取服務
        if (helloService == null) {
            helloService = IHelloService.Stub.asInterface(binder);//利用Binder 進行對象轉換
        }
        Log.e("Keiven-Chen","get helloservice Success");
        return helloService;
    }
}
package com.keiven.binder;
import android.os.RemoteException;
public class HelloService {
    private HelloService() {
    }
    public static HelloService getInstance() {
        return SingleHolder.instance;
    }
    private static class SingleHolder {
        private static HelloService instance = new HelloService(); //用于單例管理
    }
    public void helloWorld() { //這個類是應用程序想調用的
        try {
            HelloManager.getHelloServiceStub().helloWorld();//調用aidl 的方法,實現跨進程調用
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

到這里SDK 的邏輯代碼完成,還需要編譯成Jar包,在Android Studio 中可以利用Gradle腳本完成編譯,這里我將該文件編譯成二進制的dex Jar;下面是gradle 核心代碼;

------> build.gradle
android {
......
    defaultConfig {
            applicationId "com.keiven.binder" //defaultConfig 域中applicationId 值很重要,后續會用到
            ......
    }
}
......
task class_jar(type: Jar) {
    from "build/intermediates/classes/release/" //核心,將.class 文件編譯成Jar包
    from 'src/main/aidl/'
    archiveName 'sdk_tmp.jar'
    doFirst{
        println "Generate sdk_tmp.jar..."
    }
}
//Convert the class file to dex file
task dx_jar(dependsOn:class_jar,type:Exec) {
    workingDir 'build/libs'
    def osType = System.getProperty("os.name").toUpperCase();
    if(osType.contains("LINUX")){
        commandLine 'bash', '-c', "dx --dex --output=sdk_test.jar sdk_tmp.jar"
    }else if(osType.contains("WINDOWS")){
        commandLine 'cmd', '/c', "dx --dex --output=sdk_test.jar sdk_tmp.jar"
    }
    doFirst{
        println "Generate sdk_test.jar..."
    }
}
//Delete the temp jar
task jar(dependsOn: dx_jar, type: Exec) {
    workingDir 'build/libs'
    def osType = System.getProperty("os.name").toUpperCase();
    if(osType.contains("LINUX")){
        commandLine 'bash', '-c', "rm -rf sdk_tmp.jar"
    }else if(osType.contains("WINDOWS")){
        commandLine 'cmd', '/c', "del sdk_tmp.jar"
    }
    doFirst{
        println "Delete sdk_tmp.jar..."
    }
}

腳本根據操作系統的不同執行不同的命令;生成的Jar包為sdk_test.jar,可以通過在Android Studio的Terminal中運行gradlew build jar進行編譯到這里我們的SDK Jar 包制作完成,我們如何使用這個SDK 呢???由于這是一個二進制的dex Jar,所以無法直接在gradle腳本的dependencies選項中直接使用,需要通過代理jar 包的形式,就像我們的前面在我們自己的類中導入ServiceManager一樣;

SDK Jar包制作完成后,這個Jar包不是直接給應用程序調用,我們將它預制到系統目錄/system/jar/

3.2 封裝普通SDK Jar代理二進制SDK

即為二進制sdk_test.jar 生成一個代理jar
代理Jar包源碼很簡單,前面AIDL文件中定義了helloWorld()方法在HelloService.java 的helloWorld方法中被調用,應用程序通過執行HelloService.java 中的helloWorld來執行AIDL的helloWorld方法,但是前面的HelloService.java被編譯成二進制 Jar,無法被應用程序訪問,所以必須要對HelloService.java 進行代理;代理的方式就像前面的ServiceManager.java 代理一樣,代理類和被代理類的包名,類名必須一模一樣;所以這里新建的HelloService.java的包名和和類名都必須和前面編譯二進制Jar包工程中的HelloService.java一模一樣,包和類新建完成后,一般針對應用程序需要調用的接口進行代理,這里應用程序想調用helloWorld()方法,所以就代理helloWorld()方法,可以在代理方法中
直接throw new RuntimeException() 或者返回null都可以;


代理jar.png
------> 代理HelloService.java
package com.keiven.binder;
public final class HelloService {
    private HelloService(){
        throw new RuntimeException();
    }
    public static HelloService getInstance() {
        throw new RuntimeException();
    }
    public void helloWorld() { //代理接口
        throw new RuntimeException();
    }
}

沒錯,代理文件就是這么簡單,總結起來就是代理類需要和被代理類包名,類名一模一樣,代理類中實現需要被代理的方法,方法實現很簡單,直接throw new RuntimeException() 或者return null 都可以

------>代理類的build.gradle
android {
......
   defaultConfig {
          applicationId "com.keiven.binder" //很重要,和二進制Jar包的applicationId一樣
        ......
   }
}
//Actually created the .jar file
task jar(type: Jar) {
    //from android.sourceSets.main.java
    from 'build/intermediates/classes/release/'
    archiveName 'proxyjar.jar'
}

這里將代理類HelloService打包成普通的Jar包供應用程序調用;到這里代理Jar包創建成功;代理Jar包的名字為proxyjar.jar,我們可以新建應用,在應用的依賴中添加這個proxyjar.jar就可以使用里面的方法;

app ------ > MainActivity.java
package com.dynamictest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.keiven.binder.HelloService;
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            HelloService.getInstance().helloWorld();//核心,跨進程調用
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上述代碼HelloService.getInstance().helloWorld()的調用流程如下:
MainActivity.java——>HelloService.java(代理)——>HelloService.java——>IHelloService.java(AIDL生成)——>IBnHelloService.cpp(onTransact)——>HelloService.cpp;

應用程序從HelloService.java(代理)調用到系統的HelloService.java,必須說明應用程序訪問的目標,即需要在應用程序的AndroidManifest.xml中配置<uses-library>xxxxxx</uses-library>,"xxxxxx"是庫的applicationId,是不是很熟悉,前面是在sdk_test.jar和proxyjar.jar工程中配置的applicationId為"com.keiven.binder"; 所以需要在應用程序的AndroidManifest.xml的application中添加如下代碼:
<uses-library android:name="com.keiven.binder" android:required="true"/>

到這里我們的二進制Jar,代理Jar,服務端可執行程序(用于向ServiceManager注冊),APP
一應俱全,把相應的Jar包和可執行程序push 到系統對應位置,先運行helloserver,在終端中用adb install APP,會彈出 INSTALL_FAILED_MISSING_SHARED_LIBRARY,什么???應用安裝不成功,把AndroidManifest.xml中的<uses-library>去掉后編譯能正常安裝,問題肯定出在uses-library這個標簽,網上看了幾圈,找到了眉目,由于我們是sdk_test.jar我們是push 到/system/jar目錄下面的,外部應用程序需要訪問這個庫就需要在目錄/etc/permissions/下的platform.xml 配置相關內容(真正編譯ROM時需要去frameworks/base/data/etc目錄下的platform.xml修改編譯生效):

platform.png

在platform.xml文件中有如下注釋:This is a list of all the libraries available for applicationcode to link against.,標明給應用使用的Library 都需要在這里配置;

adb pull 出手機里面的platform.xml文件,在該文件中添加上述截圖代碼后adb push 回相關位置,重啟手機,重新adb install APP,成功安裝;

應用安裝成功后adb shell 進手機目錄,1:到/system/bin目錄下執行helloserver;2:啟動APP;shell 終端中會輸出如下截圖內容,這些內容是在native Service中調用prinf打印的;


Log.png

自此完成了從Java層應用程序到native Service的完整調用過程,總結起來步驟如下:
1:建立native Service;
2:根據 native Service中IMPLEMENT_META_INTERFACE聲明創建AIDL;封裝二進制Jar;
3:制作代理Jar;
4:應用程序訪問;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,761評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,829評論 18 139
  • 1:InputChannel提供函數創建底層的Pipe對象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,360評論 0 18
  • 最近加入了一個讀書會,讀的第一本書就是之前被安利無數遍的《天才在左瘋子在右》,這本書居然是一位中學輟學的人寫的。我...
    遇見生命的美好閱讀 1,192評論 0 2
  • 我這次回正陽,感恩以前同事于洪在她家里親自做了一桌好菜,召集了老家幾位好朋友,相聚一起,相互訴說思念的衷腸,回...
    雪域紅梅閱讀 133評論 0 1