开年第一篇贱贱的Android NDK服务

来源:互联网 发布:legacy安装ubuntu 编辑:程序博客网 时间:2024/06/03 23:43

首先先祝大家新年快乐,晚了一点哈,写这篇文章主要是因为项目中需要,我暂时性的做一个记录,首先要感谢LeBron_Six 这位博主对文章中技术的支持;Google 对安卓性能上的优化那也不是盖得,安卓5.0的出现已经表现出了很多,然后现在又是6.0和7.0,之前做Service所用用到的是在OnStartCommand中返回START_STICKY;然而呢,这个现在看起来并不好用,然后网上看文章,找资料,发现有解决方案,真的不错,哈哈,帮助了我很大,希望这个文章写出来能帮助你;同时这也是我对上半年的一个总结吧;上年中没更新多少,希望在今年中能给大家带来更多更好的文章吧,我一个新手的爬坑之路,希望大家能够谅解,有些问题不明白的希望能和大家一起讨论完成;废话不多说了,上干活吧。(本文中依然用as进行开发,至于为什么没用Cmake。主要是我觉得还是这个比较好,用着比较舒服习惯吧)

一.工程目录结构

这里写图片描述

二.主要的native代码

package lv.anto.com.ndktest;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;public class NativeRuntime {    private static NativeRuntime theInstance = null;      private NativeRuntime() {      }      public static NativeRuntime getInstance() {          if (theInstance == null)              theInstance = new NativeRuntime();          return theInstance;      }      /**      * RunExecutable 启动一个可自行的lib*.so文件      * @param pacaageName     * @param filename      * @param alias 别名      * @param args 参数      * @return      */      public String RunExecutable(String pacaageName, String filename, String alias, String args) {          String path = "/data/data/" + pacaageName;          String cmd1 = path + "/lib/" + filename;          String cmd2 = path + "/" + alias;          String cmd2_a1 = path + "/" + alias + " " + args;          String cmd3 = "chmod 777 " + cmd2;          String cmd4 = "dd if=" + cmd1 + " of=" + cmd2;          StringBuffer sb_result = new StringBuffer();          if (!new File("/data/data/" + alias).exists()) {            RunLocalUserCommand(pacaageName, cmd4, sb_result); // 拷贝lib/libtest.so到上一层目录,同时命名为test.              sb_result.append(";");          }          RunLocalUserCommand(pacaageName, cmd3, sb_result); // 改变test的属性,让其变为可执行          sb_result.append(";");          RunLocalUserCommand(pacaageName, cmd2_a1, sb_result); // 执行test程序.          sb_result.append(";");          return sb_result.toString();      }      /**      * 执行本地用户命令      * @param pacaageName     * @param command      * @param sb_out_Result      * @return      */      public boolean RunLocalUserCommand(String pacaageName, String command, StringBuffer sb_out_Result) {          Process process = null;          try {              process = Runtime.getRuntime().exec("sh"); // 获得shell进程              DataInputStream inputStream = new DataInputStream(process.getInputStream());            DataOutputStream outputStream = new DataOutputStream(process.getOutputStream());            outputStream.writeBytes("cd /data/data/" + pacaageName + "\n"); // 保证在command在自己的数据目录里执行,才有权限写文件到当前目录              outputStream.writeBytes(command + " &\n"); // 让程序在后台运行,前台马上返回              outputStream.writeBytes("exit\n");              outputStream.flush();              process.waitFor();              byte[] buffer = new byte[inputStream.available()];              inputStream.read(buffer);              String s = new String(buffer);              if (sb_out_Result != null)                  sb_out_Result.append("CMD Result:\n" + s);          } catch (Exception e) {               if (sb_out_Result != null)                sb_out_Result.append("Exception:" + e.getMessage());              return false;          }          return true;      }      public native void startActivity(String compname);    public native String stringFromJNI();    public native void startService(String srvname, String sdpath);    public native int findProcess(String packname);    public native int stopService();      static {          try {              System.loadLibrary("NativeRuntime"); // 加载so库        } catch (Exception e) {              e.printStackTrace();          }      }  }

三.用javah生成编译native 出来的是NativeRuntime.h文件

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class lv_anto_com_ndktest_NativeRuntime */#ifndef _Included_lv_anto_com_ndktest_NativeRuntime#define _Included_lv_anto_com_ndktest_NativeRuntime#ifdef __cplusplusextern "C" {#endif/* * Class:     lv_anto_com_ndktest_NativeRuntime * Method:    startActivity * Signature: (Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_lv_anto_com_ndktest_NativeRuntime_startActivity  (JNIEnv *, jobject, jstring);/* * Class:     lv_anto_com_ndktest_NativeRuntime * Method:    stringFromJNI * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_lv_anto_com_ndktest_NativeRuntime_stringFromJNI  (JNIEnv *, jobject);/* * Class:     lv_anto_com_ndktest_NativeRuntime * Method:    startService * Signature: (Ljava/lang/String;Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_lv_anto_com_ndktest_NativeRuntime_startService  (JNIEnv *, jobject, jstring, jstring);/* * Class:     lv_anto_com_ndktest_NativeRuntime * Method:    findProcess * Signature: (Ljava/lang/String;)I */JNIEXPORT jint JNICALL Java_lv_anto_com_ndktest_NativeRuntime_findProcess  (JNIEnv *, jobject, jstring);/* * Class:     lv_anto_com_ndktest_NativeRuntime * Method:    stopService * Signature: ()I */JNIEXPORT jint JNICALL Java_lv_anto_com_ndktest_NativeRuntime_stopService  (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif

四.配置文件Android.mk 和Application.mk;

Android.mk

LOCAL_PATH := (callmydir)include(CLEAR_VARS)
LOCAL_MODULE := NativeRuntime
LOCAL_SRC_FILES := NativeRuntime.c
include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := all

五.上面弄完了写下.C文件的编写了,主代码来了,别眨眼啊;

//// Created by mac on 16/12/30.//#include <string.h>#include <jni.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/resource.h>#include <dirent.h>#include <sys/stat.h>#include <fcntl.h>#include <pthread.h>#define PROC_DIRECTORY "/proc/"void thread(char* srvname);char* a;/** * srvname  服务名 * sd 之前创建子进程的pid写入的文件路径 */int start(int argc, char* srvname, char* sd) {    pthread_t id;    int ret;    struct rlimit r;    /**     * 第一次fork的作用是让shell认为本条命令已经终止,不用挂在终端输入上。     * 还有一个作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader)。     * 此时父进程是进程组组长。     */    int pid = fork();    if (pid < 0) {        exit(0);    } else if (pid != 0) {        //exit(0);    } else { //  第一个子进程        //int setsid = setsid();        umask(0); //使用umask修改文件的屏蔽字,为文件赋予跟多的权限,因为继承来的文件可能某些权限被屏蔽,从而失去某些功能,如读写        int pid = fork();        if (pid == 0) { // 第二个子进程            FILE  *fp;            sprintf(sd,"%s/pid",sd);            if((fp=fopen(sd,"a"))==NULL) {//打开文件 没有就创建                ftruncate(fp, 0);                lseek(fp, 0, SEEK_SET);            }            fclose(fp);            fp=fopen(sd,"rw");            if(fp>0){                char buff1[6];                int p = 0;                memset(buff1,0,sizeof(buff1));                fseek(fp,0,SEEK_SET);                fgets(buff1,6,fp);  //读取一行                if(strlen(buff1)>1){ // 有值                    kill(atoi(buff1), SIGTERM);                }            }            fclose(fp);            fp=fopen(sd,"w");            char buff[100];            int k = 3;            if(fp>0){                sprintf(buff,"%lu",getpid());                fprintf(fp,"%s\n",buff); // 把进程号写入文件            }            fclose(fp);            fflush(fp);            //step 4:修改进程工作目录为根目录,chdir(“/”).            chdir("/");            //step 5:关闭不需要的从父进程继承过来的文件描述符。            if (r.rlim_max == RLIM_INFINITY) {                r.rlim_max = 1024;            }            int i;            for (i = 0; i < r.rlim_max; i++) {                close(i);            }            umask(0);            ret = pthread_create(&id, NULL, (void *) thread, srvname);            if (ret != 0) {                printf("Create pthread error!\n");                exit(1);            }            int stdfd = open ("/dev/null", O_RDWR);            dup2(stdfd, STDOUT_FILENO);            dup2(stdfd, STDERR_FILENO);        } else {            exit(0);        }    }    return 0;}/** * 执行命令 */void ExecuteCommandWithPopen(char* command, char* out_result,        int resultBufferSize) {    FILE * fp;    out_result[resultBufferSize - 1] = '\0';    fp = popen(command, "r");    if (fp) {        fgets(out_result, resultBufferSize - 1, fp);        out_result[resultBufferSize - 1] = '\0';        pclose(fp);    } else {        exit(0);    }}/** * 检测服务,如果不存在服务则启动. * 通过am命令启动一个laucher服务,由laucher服务负责进行主服务的检测,laucher服务在检测后自动退出 */void check_and_restart_service(char* service) {    char cmdline[200];    sprintf(cmdline, "am startservice --user 0 -n %s", service);    char tmp[200];    sprintf(tmp, "cmd=%s", cmdline);    ExecuteCommandWithPopen(cmdline, tmp, 200);}void thread(char* srvname) {    while(1){        check_and_restart_service(srvname);        sleep(4);    }}jstring stoJstring(JNIEnv* env, const char* pat) {    jclass strClass = (*env)->FindClass(env, "Ljava/lang/String;");    jmethodID ctorID = (*env)->GetMethodID(env, strClass, "<init>",            "([BLjava/lang/String;)V");    jbyteArray bytes = (*env)->NewByteArray(env, strlen(pat));    (*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), (jbyte*) pat);    jstring encoding = (*env)->NewStringUTF(env, "utf-8");    return (jstring)(*env)->NewObject(env, strClass, ctorID, bytes, encoding);}/** * 判断是否是数字 */int IsNumeric(const char* ccharptr_CharacterList) {    for (; *ccharptr_CharacterList; ccharptr_CharacterList++)        if (*ccharptr_CharacterList < '0' || *ccharptr_CharacterList > '9')            return 0; // false    return 1; // true}//intCaseSensitive=0大小写不敏感int strcmp_Wrapper(const char *s1, const char *s2, int intCaseSensitive) {    if (intCaseSensitive)        return !strcmp(s1, s2);    else        return !strcasecmp(s1, s2);}//intCaseSensitive=0大小写不敏感int strstr_Wrapper(const char* haystack, const char* needle,        int intCaseSensitive) {    if (intCaseSensitive)        return (int) strstr(haystack, needle);    else        return (int) strcasestr(haystack, needle);}/** * 通过进程名称获取pid */pid_t GetPIDbyName_implements(const char* cchrptr_ProcessName,        int intCaseSensitiveness, int intExactMatch) {    char chrarry_CommandLinePath[100];    char chrarry_NameOfProcess[300];    char* chrptr_StringToCompare = NULL;    pid_t pid_ProcessIdentifier = (pid_t) - 1;    struct dirent* de_DirEntity = NULL;    DIR* dir_proc = NULL;    int (*CompareFunction)(const char*, const char*, int);    if (intExactMatch)        CompareFunction = &strcmp_Wrapper;    else        CompareFunction = &strstr_Wrapper;    dir_proc = opendir(PROC_DIRECTORY);    if (dir_proc == NULL) {        perror("Couldn't open the " PROC_DIRECTORY " directory");        return (pid_t) - 2;    }    while ((de_DirEntity = readdir(dir_proc))) {        if (de_DirEntity->d_type == DT_DIR) {            if (IsNumeric(de_DirEntity->d_name)) {                strcpy(chrarry_CommandLinePath, PROC_DIRECTORY);                strcat(chrarry_CommandLinePath, de_DirEntity->d_name);                strcat(chrarry_CommandLinePath, "/cmdline");                FILE* fd_CmdLineFile = fopen(chrarry_CommandLinePath, "rt"); //open the file for reading text                if (fd_CmdLineFile) {                    fscanf(fd_CmdLineFile, "%s", chrarry_NameOfProcess); //read from /proc/<NR>/cmdline                    fclose(fd_CmdLineFile); //close the file prior to exiting the routine                    chrptr_StringToCompare = chrarry_NameOfProcess;                    if (CompareFunction(chrptr_StringToCompare,                            cchrptr_ProcessName, intCaseSensitiveness)) {                        pid_ProcessIdentifier = (pid_t) atoi(                                de_DirEntity->d_name);                        closedir(dir_proc);                        return pid_ProcessIdentifier;                    }                }            }        }    }    closedir(dir_proc);    return pid_ProcessIdentifier;}/** * 检测服务,如果不存在服务则启动 */void check_and_restart_activity(char* service) {    char cmdline[200];    sprintf(cmdline, "am start -n %s", service);    char tmp[200];    sprintf(tmp, "cmd=%s", cmdline);    ExecuteCommandWithPopen(cmdline, tmp, 200);}JNIEXPORT jstring JNICALL Java_lv_anto_com_ndktest_NativeRuntime_stringFromJNI  (JNIEnv * env, jobject thiz){  #if defined(__arm__)        #if defined(__ARM_ARCH_7A__)            #if defined(__ARM_NEON__)                #define ABI "armeabi-v7a/NEON"            #else                #define ABI "armeabi-v7a"            #endif        #else            #define ABI "armeabi"        #endif    #elif defined(__i386__)        #define ABI "x86"    #elif defined(__mips__)        #define ABI "mips"    #else        #define ABI "unknown"    #endif    return (*env)->NewStringUTF(env,            "Hello from JNI !  Compiled with ABI " ABI ".");  }/** * jstring 转 String */char* jstringTostring(JNIEnv* env, jstring jstr) {    char* rtn = NULL;    jclass clsstring = (*env)->FindClass(env, "java/lang/String");    jstring strencode = (*env)->NewStringUTF(env, "utf-8");    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",            "(Ljava/lang/String;)[B");    jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,            strencode);    jsize alen = (*env)->GetArrayLength(env, barr);    jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);    if (alen > 0) {        rtn = (char*) malloc(alen + 1);        memcpy(rtn, ba, alen);        rtn[alen] = 0;    }    (*env)->ReleaseByteArrayElements(env, barr, ba, 0);    return rtn;}/** * 查找进程 */pid_t JNICALL Java_com_yyh_fork_NativeRuntime_findProcess(JNIEnv* env,        jobject thiz, jstring cchrptr_ProcessName) {    char * rtn = jstringTostring(env, cchrptr_ProcessName);    //return 1;    return GetPIDbyName_implements(rtn, 0, 0); //大小写不敏感 sub串匹配}/** * 启动Service */JNIEXPORT void JNICALL Java_lv_anto_com_ndktest_NativeRuntime_startService  (JNIEnv * env, jobject thiz, jstring cchrptr_ProcessName, jstring sdpath){    char * rtn = jstringTostring(env, cchrptr_ProcessName); // 得到进程名称    char * sd = jstringTostring(env, sdpath);    a = rtn;    start(1, rtn, sd);  } /** *关闭Service **/JNIEXPORT jint JNICALL Java_lv_anto_com_ndktest_NativeRuntime_stopService  (JNIEnv * env, jobject thiz){    exit(0);  }/* * Class:     lv_anto_com_ndktest_NativeRuntime * Method:    findProcess * Signature: (Ljava/lang/String;)I */JNIEXPORT jint JNICALL Java_lv_anto_com_ndktest_NativeRuntime_findProcess  (JNIEnv * env, jobject thiz, jstring cchrptr_ProcessName){    char * rtn = jstringTostring(env, cchrptr_ProcessName);    //return 1;    return GetPIDbyName_implements(rtn, 0, 0); //大小写不敏感 sub串匹配  }  JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {    JNIEnv* env = NULL;    jint result = -1;    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {        return result;    }    return JNI_VERSION_1_4;  }

六.上面的事情都干完了,我们该进行ndk-build进行编译生成.os文件啦。
七.上面的事情弄完了以后呢,我们要想起来我们还有一个gradle文件要配置啊

apply plugin: 'com.android.application'android {    compileSdkVersion 24    buildToolsVersion "24.0.2"    defaultConfig {        applicationId "lv.anto.com.ndktest"        minSdkVersion 17        targetSdkVersion 24        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"        ndk {            moduleName "NativeRuntime"         //生成的so名字            ldLibs "log"//实现__android_log_print            abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。目前可有可无。        }        sourceSets {            main {                jniLibs.srcDirs = ["libs"]            }        }    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {        exclude group: 'com.android.support', module: 'support-annotations'    })    compile 'com.android.support:appcompat-v7:24.2.1'    testCompile 'junit:junit:4.12'}

八.又弄完了,就是这么简单,哈哈,接下来就是调用了;
1.在main的主activity中调用

package lv.anto.com.ndktest;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends Activity {    Button btnstart, btnend;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initService();    }    private void initService() {        btnstart = (Button) findViewById(R.id.btn_start);        btnend = (Button) findViewById(R.id.btn_end);        Toast.makeText(this, NativeRuntime.getInstance().stringFromJNI(), Toast.LENGTH_LONG).show();        String executable = "libNativeRuntime.so";        String aliasfile = "NativeRuntime";        String parafind = "/data/data/" + getPackageName() + "/" + aliasfile;        String retx = "false";        NativeRuntime.getInstance().RunExecutable(getPackageName(), executable, aliasfile, getPackageName() + "/lv.anto.com.ndktest.HostMonitor");        NativeRuntime.getInstance().startService(getPackageName() + "/lv.anto.com.ndktest.HostMonitor", FileUtils.createRootPath());        btnstart.setOnClickListener(new View.OnClickListener() {            public void onClick(View v) {                (new Thread(new Runnable() {                    public void run() {                        try {                        } catch (Exception e) {                            e.printStackTrace();                        }                    }                })).start();            }        });        btnend.setOnClickListener(new View.OnClickListener() {            public void onClick(View v) {                try {                    NativeRuntime.getInstance().stopService();                } catch (Exception e) {                    e.printStackTrace();                }            }        });    }}

2.在BroadcastReceiver中进行配置开机启动

package lv.anto.com.ndktest;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;public class PhoneStatReceiver extends BroadcastReceiver {    private String TAG = "tag";      @Override      public void onReceive(Context context, Intent intent) {        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {              Log.i(TAG, "手机开机了~~");            NativeRuntime.getInstance().startService(context.getPackageName() +                    "/lv.anto.com.ndktest.HostMonitor"                    , FileUtils.createRootPath());        } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {          }      }  } 

下面我再把工具类的代码粘出来;

package lv.anto.com.ndktest;import java.io.File;import java.io.FileOutputStream;import android.os.Environment;/** * 文件工具类 *  * @author king *  */public class FileUtils {    // 根缓存目录    private static String cacheRootPath = "";    /**     * sd卡是否可用     *      * @return     */    public static boolean isSdCardAvailable() {        return Environment.getExternalStorageState().equals(                Environment.MEDIA_MOUNTED);    }    /**     * 创建根缓存目录     *      * @return     */    public static String createRootPath() {        if (isSdCardAvailable()) {            // /sdcard/Android/data/<application package>/cache            cacheRootPath = App.mContext.getExternalCacheDir()                    .getPath();        } else {            // /data/data/<application package>/cache            cacheRootPath = App.mContext.getCacheDir().getPath();        }        return cacheRootPath;    }    /**     * 创建文件夹     *      * @param dirPath     * @return 创建失败返回""     */    private static String createDir(String dirPath) {        try {            File dir = new File(dirPath);            if (!dir.exists()) {                dir.mkdirs();            }            return dir.getAbsolutePath();        } catch (Exception e) {            e.printStackTrace();        }        return dirPath;    }    /**     * 获取图片缓存目录     *      * @return 创建失败,返回""     */    public static String getImageCachePath() {        String path = createDir(createRootPath() + File.separator + "img"                + File.separator);        return path;    }    /**     * 获取图片裁剪缓存目录     *      * @return 创建失败,返回""     */    public static String getImageCropCachePath() {        String path = createDir(createRootPath() + File.separator + "imgCrop"                + File.separator);        return path;    }    /**     * 删除文件或者文件夹     *      * @param file     */    public static void deleteFileOrDirectory(File file) {        try {            if (file.isFile()) {                file.delete();                return;            }            if (file.isDirectory()) {                File[] childFiles = file.listFiles();                // 删除空文件夹                if (childFiles == null || childFiles.length == 0) {                    file.delete();                    return;                }                // 递归删除文件夹下的子文件                for (int i = 0; i < childFiles.length; i++) {                    deleteFileOrDirectory(childFiles[i]);                }                file.delete();            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 将内容写入文件     *      * @param filePath     *            eg:/mnt/sdcard/demo.txt     * @param content     *            内容     */    public static void writeFileSdcard(String filePath, String content,            boolean isAppend) {        try {            FileOutputStream fout = new FileOutputStream(filePath, isAppend);            byte[] bytes = content.getBytes();            fout.write(bytes);            fout.close();        } catch (Exception e) {            e.printStackTrace();        }    }}

剩下的事情大家就可以自行解决了,布局文件中只有两个按钮,别忘了配置AndroidManifest.xml文件;

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="lv.anto.com.ndktest">    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />    <application        android:name=".App"        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:supportsRtl="true"        android:theme="@style/AppTheme">        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <receiver            android:name=".PhoneStatReceiver"            android:enabled="true"            android:permission="android.permission.RECEIVE_BOOT_COMPLETED">            <intent-filter>                <action android:name="android.intent.action.BOOT_COMPLETED" />                <action android:name="android.intent.action.USER_PRESENT" />            </intent-filter>        </receiver>        <service            android:name=".HostMonitor"            android:enabled="true"            android:exported="true"            android:process=":remote" />    </application></manifest>

总结:这些就弄完了,一个贱贱的不死的服务就这样搞定了,是不是感觉很简单呢?说实话,对于C总的东西还是有些不了解的,希望以后能够进步了解,也同样希望看到这个文章的你,能够写出来这么贱的服务,对于社交类,后台服务类的app还是很有帮助的,犹如我之前写过的一片文章,我觉得这就是一种冥冥之中的趋势;哈哈,一起加油进步吧;很晚了,晚安各位;

0 0