溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

發(fā)布時(shí)間:2022-03-21 16:41:33 來(lái)源:億速云 閱讀:334 作者:iii 欄目:大數(shù)據(jù)

本文小編為大家詳細(xì)介紹“Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。

隨著 Android 移動(dòng)安全的高速發(fā)展,不管是為了執(zhí)行效率還是程序的安全性等,關(guān)鍵代碼下沉 native 層已成為基本操作。

native 層的開發(fā)就是通指的 JNI/NDK 開發(fā),通過(guò) JNI 可以實(shí)現(xiàn) java 層和 native 層(主要是 C/C++ )的相互調(diào)用,native 層經(jīng)編譯后產(chǎn)生 so 動(dòng)態(tài)鏈接庫(kù),so 文件具有可移植性廣,執(zhí)行效率高,保密性強(qiáng)等優(yōu)點(diǎn)。

那么問(wèn)題來(lái)了,如何調(diào)用 so 文件顯得異常重要,當(dāng)然你也可以直接分析 so 文件的偽代碼,利用強(qiáng)悍的編程功底直接模擬關(guān)鍵操作,但是我想對(duì)于普通人來(lái)說(shuō)頭發(fā)還是比較重要的。

當(dāng)前調(diào)用 so 文件的主流操作應(yīng)該是:

1,基于 Unicorn 的各種實(shí)現(xiàn)(還在學(xué)習(xí)中,暫且不表)  

2,Android 服務(wù)器的搭建,在 App 內(nèi)起 http 服務(wù)完成調(diào)用 so 的需求(當(dāng)然前提是過(guò)了 so 的效驗(yàn)等操作)

至于為什么選用 AndServer,好吧,不為什么,只是因?yàn)樗阉鞯搅怂? 

為什么結(jié)合 Service,在學(xué)習(xí) Android 開發(fā)的時(shí)候了解到了 Service 的生命周期,個(gè)人理解用 Service 去創(chuàng)建 Http 服務(wù)比較好。

當(dāng)然也有 Application 的簡(jiǎn)單使用,因?yàn)樵谡江h(huán)境中,大多數(shù) so 文件的邏輯中都有 context 的一些包名了,簽名了的效驗(yàn)等,自定義 Application 的話獲取 context 傳參就好了。

libyemu.so 簡(jiǎn)介

這是我編譯好的一個(gè) so 文件,就是根據(jù)入?yún)⒆鱿潞?jiǎn)單的字符串拼接(以下是 native 層編譯前的 c 代碼)

extern "C"JNIEXPORT jstring JNICALLJava_com_fw_myapplication_ndktest_NdkTest_stringFromUTF(JNIEnv *env, jobject instance, jstring str_) {    jclass String_clazz = env->FindClass("java/lang/String");    jmethodID concat_methodID = env->GetMethodID(String_clazz, "concat", "(Ljava/lang/String;)Ljava/lang/String;");    jstring str = env->NewStringUTF("  from so --[NightTeam夜幕]");    jobject str1 = env->CallObjectMethod(str_, concat_methodID, str);    const char *chars = env->GetStringUTFChars((jstring)str1, 0);    return env->NewStringUTF(chars);}

這部分代碼還是有必要貼一下的,簡(jiǎn)單的靜態(tài)注冊(cè)使用了反射的思想,反射在逆向中至關(guān)重要

接下來(lái)是 java 代碼,定義了 native 函數(shù)

package com.fw.myapplication.ndktest;public class NdkTest {    public static native String stringFromUTF(String str);    static {        System.loadLibrary("yemu");    }}

如果到這里有點(diǎn)懵逼的同學(xué)可能需要去補(bǔ)下 Android 開發(fā)基礎(chǔ)了

Android 項(xiàng)目測(cè)試 so

先說(shuō)下我的環(huán)境,因?yàn)檫@個(gè)環(huán)境影響太大了  

1,AndroidStudio 3.4  

2,手機(jī) Android 6 架構(gòu) armeabi-v7a  

打開 AndroidStudio 新建 project  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用cdn.nlark.com/yuque/0/2020/webp/97322/1607479407035-f0bfc349-9792-4b75-be4b-0b2c52f2cabc.webp?x-oss-process=image/watermark,type_d3F5LW1pY3JvaGVp,size_10,text_dGl0b2RhdGEuY29t,color_FFFFFF,shadow_50,t_80,g_se,x_10,y_10">

在 module 的 build 中加這么一句,然后 sync  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

把編譯好的 so 文件復(fù)制到 libs 文件夾下(和剛才的 jniLibs.srcDirs 對(duì)應(yīng))  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

把 so 對(duì)應(yīng)的 java 代碼也 copy 過(guò)來(lái),注意包名類名的一致性  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

打開 activity_main.xml 文件為 TextView 添加 id  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

打開 MainActiviy.java 開始編碼  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

這兩行的意思就是,先從布局中找到對(duì)應(yīng) id 的 TextView,然后為其設(shè)置 Text(調(diào)用 native 函數(shù)的返回值)  

下面測(cè)試一下咱們的 so 調(diào)用情況  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

可以看到咱們的 so 文件調(diào)用成功(這里咱們的 so 沒(méi)有效驗(yàn),只是測(cè)試 app 是否可以正常調(diào)用)

AndServer 代碼編寫

AndServer 官方文檔:https://yanzhenjie.com/AndServer/  

打開官方文檔,看看人家的入門介紹,新建 java 文件  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

如圖經(jīng)典 MVC 的 C 就寫好了,定義了一個(gè) nightteam_sign 接口,請(qǐng)求方式為 get,請(qǐng)求參數(shù)為 sign,調(diào)用 native 函數(shù),然后返回 json,但是這里我想利用 Application 獲取下 context 對(duì)象,取下包名,接下來(lái)自定義 Applictaion

package com.nightteam.httpso;import android.app.Application;public class MyApp extends Application {    private static MyApp myApp;    public static MyApp getInstance() {        return myApp;    }    @Override    public void onCreate() {        super.onCreate();        myApp = this;    }}

然后在 manifest 文件中指定要啟動(dòng)的 Application  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

然后修改 MyController.java 的代碼  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

接下來(lái)把官方文檔-服務(wù)器的代碼 copy 下來(lái)  

導(dǎo)入一些包,修改部分代碼如下  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

新版本的 AndServer.serverBuilder 已經(jīng)需要傳遞 context 了,這里把網(wǎng)絡(luò)地址和端口號(hào)也修改為從構(gòu)造參數(shù)中獲取,到這里 AndServer 的東西基本完了,實(shí)際上咱們就搭建一個(gè)調(diào) so 的接口,并沒(méi)有過(guò)多的業(yè)務(wù)邏輯,所以代碼就是使用的最簡(jiǎn)單的

Service 代碼編寫

咱們這里用按鈕的點(diǎn)擊事件啟動(dòng) Service,故在 activity_main.xml 中添加一個(gè) button 并指定點(diǎn)擊事件  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

接下來(lái)編寫自定義 Service 代碼

package com.nightteam.httpso.Service;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.util.Log;import com.nightteam.httpso.ServerManager;import java.net.InetAddress;import java.net.UnknownHostException;public class MyService extends Service {    private static final String TAG = "NigthTeam";    @Override    public void onCreate() {        super.onCreate();        Log.d(TAG, "onCreate: MyService");        new Thread() {            @Override            public void run() {                super.run();                InetAddress inetAddress = null;                try {                    inetAddress = InetAddress.getByName("0.0.0.0");                    Log.d(TAG, "onCreate: " + inetAddress.getHostAddress());                    ServerManager serverManager = new ServerManager(getApplicationContext(), inetAddress, 8005);                    serverManager.startServer();                } catch (UnknownHostException e) {                    e.printStackTrace();                }            }        }.start();    }    @Override    public IBinder onBind(Intent intent) {        return null;    }}

打上了幾個(gè) log,在子線程中啟動(dòng) AndServer 的服務(wù)(何時(shí)使用 UI 線程和子線程是 Android 基礎(chǔ),這里就不贅述了)  

注意一下,這里從 0.0.0.0 獲取 inetAddress,可不要寫錯(cuò)了,localhost 和 0.0.0.0 的區(qū)別請(qǐng)移步搜索引擎  

然后就是向 ServerManager 的構(gòu)造函數(shù)傳遞 context,inetAddress,port 用來(lái) new 對(duì)象,隨后開啟服務(wù)

最后注意檢查下 manifest 文件中 Service 的聲明  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

開啟 Service,并獲取本機(jī) ip

回到我們的 MainActivity.java 的 operate( button 的點(diǎn)擊事件)編寫啟動(dòng) Service 代碼

public void operate(View view) {        switch (view.getId()){            case R.id.id_bt_index:                //啟動(dòng)服務(wù):創(chuàng)建-->啟動(dòng)-->銷毀                //如果服務(wù)已經(jīng)創(chuàng)建了,后續(xù)重復(fù)啟動(dòng),操作的都是同一個(gè)服務(wù),不會(huì)再重新創(chuàng)建了,除非你先銷毀它                Intent it1 = new Intent(this, MyService.class);                Log.d(TAG, "operate: button");                startService(it1);                ((Button) view).setText("服務(wù)已開啟");                break;        }    }

到這里我們的服務(wù)基本搭建好了,但是為了方便起見,我想把咱們的本機(jī) ip 顯示在 App 上,這樣我們就不用去設(shè)置再查看了  

我在網(wǎng)上找到了一個(gè)獲取 ip 地址的一個(gè)工具類,源碼如下:

package com.nightteam.httpso;import java.net.InetAddress;import java.net.NetworkInterface;import java.net.SocketException;import java.util.Enumeration;import java.util.regex.Pattern;public class NetUtils {    private static final Pattern IPV4_PATTERN = Pattern.compile("^(" +            "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" +            "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");    private static boolean isIPv4Address(String input) {        return IPV4_PATTERN.matcher(input).matches();    }    //獲取本機(jī)IP地址    public static InetAddress getLocalIPAddress() {        Enumeration<NetworkInterface> enumeration = null;        try {            enumeration = NetworkInterface.getNetworkInterfaces();        } catch (SocketException e) {            e.printStackTrace();        }        if (enumeration != null) {            while (enumeration.hasMoreElements()) {                NetworkInterface nif = enumeration.nextElement();                Enumeration<InetAddress> inetAddresses = nif.getInetAddresses();                if (inetAddresses != null)                    while (inetAddresses.hasMoreElements()) {                        InetAddress inetAddress = inetAddresses.nextElement();                        if (!inetAddress.isLoopbackAddress() && isIPv4Address(inetAddress.getHostAddress())) {                            return inetAddress;                        }                    }            }        }        return null;    }}

把工具類 copy 到我們的 Android 項(xiàng)目中,繼續(xù)在 MainActivity.java 中編碼  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

獲取了一下本機(jī)地址和 Android SDK 版本( Android 8 之后啟動(dòng) Service 方式不一樣)

申請(qǐng)權(quán)限,啟動(dòng) App

最后一步就是為 app 申請(qǐng)網(wǎng)絡(luò)權(quán)限了  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

隨后連接我們的手機(jī),運(yùn)行項(xiàng)目,測(cè)試一下,點(diǎn)擊開啟服務(wù)  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

看下 AndroidStudio 日志  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

好像一切正常,在瀏覽器訪問(wèn)下試試( ip 就是 App 中顯示的 ip 地址)  

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

如圖正常訪問(wèn)到了我們想要的內(nèi)容

回過(guò)頭來(lái)說(shuō)下 Service,打開我們手機(jī)的設(shè)置,找到應(yīng)用程序管理-運(yùn)行中的服務(wù)(手機(jī)不同,方式不同)

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

可以看到我們的程序,運(yùn)行了一個(gè)服務(wù),這個(gè)服務(wù)就是咱們編碼的 MyService

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

接下來(lái)殺掉該 App進(jìn)程,再次查看運(yùn)行中的服務(wù)

Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用

讀到這里,這篇“Android服務(wù)器怎么實(shí)現(xiàn)so文件調(diào)用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過(guò)才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI