[Android]使用函數(shù)指針實(shí)現(xiàn)native層異步回調(diào)

1. 前言

在上篇關(guān)于lambda表達(dá)式實(shí)現(xiàn)方式的文章中,有提到一個(gè)概念叫做MethodHandle切端,當(dāng)時(shí)的解釋是類(lèi)似于C/C++的函數(shù)指針子巾,但是文章發(fā)出后咨詢(xún)友人的意見(jiàn)帆赢,發(fā)現(xiàn)很多人并不清楚函數(shù)指針是怎么用的线梗,其實(shí)我本人也是只是知道這個(gè)概念,但是并沒(méi)有實(shí)際使用過(guò)仪搔。仿佛冥冥中自有天意,前幾天公司的項(xiàng)目正好用到了函數(shù)指針來(lái)做native層的事件回調(diào)烤咧,也讓我理解了函數(shù)指針的妙用。但是關(guān)于C/C++我并不是特別熟練煮嫌,于是將實(shí)現(xiàn)過(guò)程寫(xiě)了個(gè)DEMO,一是為了做個(gè)記錄熟悉過(guò)程昌阿,二是以備后續(xù)使用恳邀。

2. 概念

如果在程序中定義了一個(gè)函數(shù),那么在編譯時(shí)系統(tǒng)就會(huì)為這個(gè)函數(shù)代碼分配一段存儲(chǔ)空間谣沸,這段存儲(chǔ)空間的首地址稱(chēng)為這個(gè)函數(shù)的地址。而且函數(shù)名表示的就是這個(gè)地址乳附。既然是地址我們就可以定義一個(gè)指針變量來(lái)存放伴澄,這個(gè)指針變量就叫作函數(shù)指針變量赋除,簡(jiǎn)稱(chēng)函數(shù)指針非凌。

那么這個(gè)指針變量怎么定義呢?雖然同樣是指向一個(gè)地址清焕,但指向函數(shù)的指針變量同我們之前講的指向變量的指針變量的定義方式是不同的。例如:

int(*p)(int, int);

這個(gè)語(yǔ)句就定義了一個(gè)指向函數(shù)的指針變量 p秸妥。首先它是一個(gè)指針變量,所以要有一個(gè)“”粥惧,即(p);其次前面的 int 表示這個(gè)指針變量可以指向返回值類(lèi)型為 int 型的函數(shù)突雪;后面括號(hào)中的兩個(gè) int 表示這個(gè)指針變量可以指向有兩個(gè)參數(shù)且都是 int 型的函數(shù)。所以合起來(lái)這個(gè)語(yǔ)句的意思就是:定義了一個(gè)指針變量 p咏删,該指針變量可以指向返回值類(lèi)型為 int 型,且有兩個(gè)整型參數(shù)的函數(shù)督函。p 的類(lèi)型為 int(*)(int,int)辰狡。

所以函數(shù)指針的定義方式為:

函數(shù)返回值類(lèi)型 (* 指針變量名) (函數(shù)參數(shù)列表);

“函數(shù)返回值類(lèi)型”表示該指針變量可以指向具有什么返回值類(lèi)型的函數(shù);“函數(shù)參數(shù)列表”表示該指針變量可以指向具有什么參數(shù)列表的函數(shù)宛篇。這個(gè)參數(shù)列表中只需要寫(xiě)函數(shù)的參數(shù)類(lèi)型即可。

我們看到叫倍,函數(shù)指針的定義就是將“函數(shù)聲明”中的“函數(shù)名”改成“(指針變量名)”嘿般。但是這里需要注意的是:“(指針變量名)”兩端的括號(hào)不能省略涯冠,括號(hào)改變了運(yùn)算符的優(yōu)先級(jí)。如果省略了括號(hào)蛇更,就不是定義函數(shù)指針而是一個(gè)函數(shù)聲明了,即聲明了一個(gè)返回值類(lèi)型為指針型的函數(shù)派任。

那么怎么判斷一個(gè)指針變量是指向變量的指針變量還是指向函數(shù)的指針變量呢?首先看變量名前面有沒(méi)有“”掌逛,如果有“”說(shuō)明是指針變量;其次看變量名的后面有沒(méi)有帶有形參類(lèi)型的圓括號(hào)豆混,如果有就是指向函數(shù)的指針變量,即函數(shù)指針皿伺,如果沒(méi)有就是指向變量的指針變量。

3. 定義函數(shù)指針和枚舉

假設(shè)native層有個(gè)耗時(shí)操作需要異步調(diào)用鸵鸥,我們?cè)诋惒秸{(diào)用結(jié)束后通過(guò)回調(diào)通知業(yè)務(wù)層完成事件,那么這個(gè)時(shí)候就可以使用函數(shù)指針作為回調(diào)方法妒穴。

定義方式:

  1. 首先定義事件枚舉:
enum EventEnum {
    eeSleepWake,
};
  1. 其次摊崭,定義一個(gè)函數(shù)指針:
typedef void (*onSleepWake)(int code, void* sender);

這個(gè)函數(shù)指針可以指向一個(gè)返回值為void 參數(shù)分別為 int 和void型指針的函數(shù)讼油,其中void型指針表示調(diào)用方的指針

  1. 定義一個(gè)結(jié)構(gòu)體呢簸,包含函數(shù)指針和調(diào)用方的指針
struct EventData {
    void* eventPointer;
    void* sender;
};
  1. 注冊(cè)事件持有類(lèi),使其成為單例

這個(gè)操作的部分代碼:

class EventManager {
public:
    static EventManager& singleton()
    {
        static EventManager sl;
        return sl;
    }
    static EventManager& getInstance()
    {
        return singleton();
    }

    //注冊(cè)事件
    void addEvent(EventEnum eventEnum, void* event, void* sender);

    EventData getEventData(EventEnum eventEnum);

private:
    std::map<EventEnum, EventData> eventMap;
    EventManager(){};
    ~EventManager(){};
};
  1. 實(shí)現(xiàn)事件注冊(cè)函數(shù)
void EventManager::addEvent(EventEnum eventEnum, void* event, void* sender) {
    if(event == nullptr || sender == nullptr) {
        return;
    }
    EventData eventData;
    eventData.eventPointer = event;
    eventData.sender = sender;

    eventMap.insert(std::pair<EventEnum, EventData>(eventEnum, eventData));
}
  1. 編寫(xiě)函數(shù)指針對(duì)應(yīng)函數(shù)的具體實(shí)現(xiàn)
void eeSleepWakeCallback(int result, void* sender) {
    JniTester *tester = (JniTester *) sender;
    tester->onResultCallback(result);
}
  1. 在入口類(lèi)中注冊(cè)事件及其對(duì)應(yīng)的枚舉和函數(shù)
JniTester::JniTester() {
    EventManager::getInstance().addEvent(eeSleepWake, (void*)eeSleepWakeCallback, this);
}
  1. 編寫(xiě)異步函數(shù)調(diào)用
    ···
    void JniTester::getThreadResult() {
    ThreadTest *test = new ThreadTest();
    test->sleepThread();
    }
    ···
    耗時(shí)函數(shù)的具體實(shí)現(xiàn):
void ThreadTest::sleepThread() {
    std::thread cal_task(&ThreadTest::makeSleep, this);
    cal_task.detach();
}

void ThreadTest::makeSleep() {
    sleep(2);
}

這一步我們是通過(guò)新建一個(gè)線(xiàn)程嘿架,并讓其等待2S來(lái)模擬異步耗時(shí)操作

4. 異步回調(diào)的實(shí)現(xiàn)

  1. 在java層編寫(xiě)java的回調(diào)方法
private OnResultCallback callback;

    public void setOnResultCallback(OnResultCallback callback) {
        this.callback = callback;
    }

    public interface OnResultCallback {
        void onResult(int result);
    }
  1. 在java曾編寫(xiě)java層回調(diào)的觸發(fā):
    public void onResult(int result) {
        if (this.callback != null) {
            callback.onResult(result);
        }
    }
  1. native層異步動(dòng)作完成的通知

通過(guò)向單例的事件持有類(lèi)獲取對(duì)應(yīng)的事件枚舉啸箫,獲取到其對(duì)應(yīng)的函數(shù)指針耸彪,并調(diào)用該函數(shù)指針實(shí)現(xiàn):

void ThreadTest::makeSleep() {
    sleep(2);
    EventData eventData = EventManager::singleton().getEventData(eeSleepWake);
    onSleepWake wake = (onSleepWake)eventData.eventPointer;
    if(wake) {
        wake(12345, eventData.sender);
    }
}

因?yàn)槲覀冊(cè)诘谌鹿?jié)第7步注冊(cè)的函數(shù)指針是eeSleepWakeCallback忘苛, 因此唱较,這里會(huì)調(diào)用到這個(gè)函數(shù):

void eeSleepWakeCallback(int result, void* sender) {
    JniTester *tester = (JniTester *) sender;
    tester->onResultCallback(result);
}

通過(guò)sender確定具體的對(duì)象,調(diào)用其onResultCallback函數(shù)

  1. onResultCallback函數(shù)的實(shí)現(xiàn)
void JniTester::onResultCallback(int result) {
    JNIEnv *env = NULL;
    int status = f_jvm->GetEnv((void **) &env, JNI_VERSION_1_4);

    bool isInThread = false;
    if (status < 0) {
        isInThread = true;
        f_jvm->AttachCurrentThread(&env, NULL);
    }

    if (f_cls != NULL) {
        jmethodID id = env->GetMethodID(f_cls, "onResult", "(I)V");
        if (id != NULL) {
            env->CallVoidMethod(f_obj, id, result);
        }
    }

    if (isInThread) {
        f_jvm->DetachCurrentThread();
    }
}

這里因?yàn)槿鄙賘ava環(huán)境南缓,因此我們需要將該線(xiàn)程掛載到j(luò)vm上執(zhí)行荧呐,并獲取對(duì)應(yīng)的JNIEnv 汉形,通過(guò)jnienv獲取java層的回調(diào)觸發(fā)方法onResult并執(zhí)行倍阐。

5.效果

編寫(xiě)測(cè)試代碼:

        JniTester tester = new JniTester();
        Log.d("zyl", "startTime = " + System.currentTimeMillis());
        tester.setOnResultCallback(result -> {
            Log.d("zyl", "endTime = " + System.currentTimeMillis());
            Log.d("zyl", "result = " + result);
        });
        tester.requestData();

執(zhí)行結(jié)果:


image.png

和預(yù)期一致,完美峰搪。

完整版代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市概耻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鞠柄,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件春锋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡期奔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)馁痴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肺孤,你說(shuō)我怎么就攤上這事≡拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵酬屉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我呐萨,道長(zhǎng)杀饵,這世上最難降的妖魔是什么谬擦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮惨远,結(jié)果婚禮上谜悟,老公的妹妹穿的比我還像新娘锨络。我一直安慰自己狼牺,他們只是感情好羡儿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布是钥。 她就那樣靜靜地躺著,像睡著了一般悄泥。 火紅的嫁衣襯著肌膚如雪虏冻。 梳的紋絲不亂的頭發(fā)上弹囚,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音鸥鹉,去河邊找鬼。 笑死毁渗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灸异。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肺樟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了么伯?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蹦狂,失蹤者是張志新(化名)和其女友劉穎朋贬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體锦募,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年糠亩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了准验。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赎线。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡糊饱,死狀恐怖垂寥,靈堂內(nèi)的尸體忽然破棺而出另锋,到底是詐尸還是另有隱情,我是刑警寧澤夭坪,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站室梅,受9級(jí)特大地震影響戏仓,放射性物質(zhì)發(fā)生泄漏亡鼠。R本人自食惡果不足惜柜去,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一拆宛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浑厚,春花似錦、人聲如沸钳幅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春震嫉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背票堵。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悴势,地道東北人窗宇。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓特纤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捧存。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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