欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

AndroidNDK開發(fā)(八)——應(yīng)用監(jiān)聽自身卸載,彈出用戶反 - 新聞資訊 - 云南小程序開發(fā)|云南軟件開發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開發(fā)/軟件開發(fā)

知識(shí)

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!

您當(dāng)前位置>首頁(yè) » 新聞資訊 » 技術(shù)分享 >

AndroidNDK開發(fā)(八)——應(yīng)用監(jiān)聽自身卸載,彈出用戶反

發(fā)表時(shí)間:2021-1-10

發(fā)布人:葵宇科技

瀏覽次數(shù):23


轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/allen315410/article/details/42521251

監(jiān)聽卸載情景和原理分析

1,情景分析


        在上上篇博客中我寫了一下NDK開發(fā)實(shí)踐項(xiàng)目,使用開源的LAME庫(kù)轉(zhuǎn)碼MP3,作為前面幾篇基礎(chǔ)博客的加深理解使用的,但是這樣的項(xiàng)目用處不大,除了練練NDK功底。這篇博客,我將講述一下一個(gè)各大應(yīng)用中很常見的一個(gè)功能,同樣也是基于JNI開發(fā)的Android應(yīng)用小Demo,看完這個(gè)之后,不僅可以加深對(duì)NDK開發(fā)的理解,而且該Demo也可以使用在實(shí)際的開發(fā)中。不知道大家在使用一個(gè)Android應(yīng)用的時(shí)候,當(dāng)我們卸載這個(gè)應(yīng)用后,設(shè)備上會(huì)彈出一個(gè)“用戶反饋調(diào)查”的網(wǎng)頁(yè)出來,也許很多人沒有留意過或者直接忽視了,那么從現(xiàn)在開始請(qǐng)留意,大家不妨下載一下“豌豆莢”“360”之類的應(yīng)用裝上,然后卸載,看看設(shè)備上有沒有彈出瀏覽器,瀏覽器上打開的“XXX用戶反饋”?上面寫了一些HTML表單,問我們“你為毛要卸載我們這么好的應(yīng)用啊?”“我們哪里得罪你了?”“卸載之后,你丫的還裝不?”,呵呵,開個(gè)玩笑,實(shí)際效果如下圖:
[img]http://img.blog.csdn.net/20150108104422098
       好了,上面的圖片是感覺似曾顯示???那么這樣的一個(gè)小功能是怎么實(shí)現(xiàn)的呢?我們先從Java層以我們有的Android基礎(chǔ)分析一下:
1,監(jiān)聽系統(tǒng)的卸載廣播,但是這個(gè)只能監(jiān)聽其他應(yīng)用的卸載廣播的動(dòng)作,通過卸載廣播監(jiān)聽自己是監(jiān)聽不到的:失敗
2,系統(tǒng)配置文件,做一個(gè)標(biāo)記應(yīng)用是否卸載,判斷標(biāo)記來show用戶反饋,顯然這也是不合理的,因?yàn)閼?yīng)用卸載之后,配置文件也沒有了。
3,靜默安裝另一個(gè)程序,監(jiān)聽自己的應(yīng)用被卸載的動(dòng)作。前提是要root,才能實(shí)現(xiàn)。但是市場(chǎng)絕大多數(shù)手機(jī)都是默認(rèn)沒有root權(quán)限的。
4,服務(wù)檢測(cè),只能是自己開啟,當(dāng)自身被卸載了,服務(wù)也一并被干掉了。
以上幾點(diǎn)看起來都無法實(shí)現(xiàn)這個(gè)功能,確實(shí)如此啊,單純的從Java層是做不到這一點(diǎn)的。

2,原理分析


       上面情景分析后表明Java實(shí)現(xiàn)不了這樣的一個(gè)功能,是否該考慮一下使用JNI了,用C在底層為我們實(shí)現(xiàn)這樣一個(gè)打開內(nèi)置瀏覽器加載用戶反饋網(wǎng)頁(yè)即可,在知道這個(gè)方法之前,我們有必要了解以下幾個(gè)知識(shí)點(diǎn)。
1.通過c語(yǔ)言,c進(jìn)程監(jiān)視。
    既然Java做不到的話,我們?cè)囍褂肅語(yǔ)言在底層實(shí)現(xiàn)好了,讓C語(yǔ)言調(diào)用Android adb的命令去打開內(nèi)置瀏覽器。
判斷自己是否被卸載
andoird程序在被安裝的時(shí)候會(huì)在/data/data/目錄下生成一個(gè)以為包名為文件名的目錄/data/data/包名
監(jiān)聽該目錄是否還存在,如果不存在,就證明應(yīng)用被卸載了。

2.c代碼可以復(fù)制一個(gè)當(dāng)前的進(jìn)程作為自己的兒子,父進(jìn)程銷毀的時(shí)候,子進(jìn)程還存在。
fork()函數(shù):
        fork()函數(shù)通過系統(tǒng)調(diào)用創(chuàng)建一個(gè)與原來進(jìn)程幾乎完全相同的進(jìn)程,兩個(gè)進(jìn)程可以做相同的事,相當(dāng)于自己生了個(gè)兒子,如果初始參數(shù)或者傳入的參數(shù)不一樣,兩個(gè)進(jìn)程做的事情也不一樣。當(dāng)前進(jìn)程調(diào)用fork函數(shù)之后,系統(tǒng)先給當(dāng)前進(jìn)程分配資源,然后再將當(dāng)前進(jìn)程的所有變量的值復(fù)制到新進(jìn)程中(只有少數(shù)值不一樣),相當(dāng)于克隆了一個(gè)自己。
       pid_t fpid = fork()被調(diào)用前,就一個(gè)進(jìn)程執(zhí)行該段代碼,這條語(yǔ)句執(zhí)行之后,就將有兩個(gè)進(jìn)程執(zhí)行代碼,兩個(gè)進(jìn)程執(zhí)行沒有固定先后順序,主要看系統(tǒng)調(diào)度策略,fork函數(shù)的特別之處在于調(diào)用一次,但是卻可以返回兩次,甚至是三種的結(jié)果
(1)在父進(jìn)程中返回子進(jìn)程的進(jìn)程id(pid)
(2)在子進(jìn)程中返回0
(3)出現(xiàn)錯(cuò)誤,返回小于0的負(fù)值
出現(xiàn)錯(cuò)誤原因:(1)進(jìn)程數(shù)已經(jīng)達(dá)到系統(tǒng)規(guī)定 (2)內(nèi)存不足,此時(shí)返回



3.在c代碼的子進(jìn)程中監(jiān)視父進(jìn)程是否被卸載,如果被卸載,通知Android系統(tǒng)打開一個(gè)url,卸載調(diào)查的網(wǎng)頁(yè)。
AM命令

        Android系統(tǒng)提供的adb工具,在adb的基礎(chǔ)上執(zhí)行adb shell就可以直接對(duì)android系統(tǒng)執(zhí)行shell命令
        am命令:在Android系統(tǒng)中通過adb shell 啟動(dòng)某個(gè)Activity、Service、撥打電話、啟動(dòng)瀏覽器等操作Android的命令。
        am命令的源碼在Am.java中,在shell環(huán)境下執(zhí)行am命令實(shí)際是啟動(dòng)一個(gè)線程執(zhí)行Am.java中的主函數(shù)(main方法),am命令后跟的參數(shù)都會(huì)當(dāng)做運(yùn)行時(shí)參數(shù)傳遞到主函數(shù)中,主要實(shí)現(xiàn)在Am.java的run方法中。
        am命令可以用start子命令,和帶指定的參數(shù),start是子命令,不是參數(shù)
常見參數(shù):-a:表示動(dòng)作,-d:表示攜帶的數(shù)據(jù),-t:表示傳入的類型,-n:指定的組件名

例如,我們現(xiàn)在在命令行模式下進(jìn)入adb shell下,使用這個(gè)命令去打開一個(gè)網(wǎng)頁(yè)
[img]http://img.blog.csdn.net/20150108115851865
類似的命令還有這些:
撥打電話
命令:am start -a android.intent.action.CALL -d tel:電話號(hào)碼
示例:am start -a android.intent.action.CALL -d tel:10086
打開一個(gè)網(wǎng)頁(yè)
命令:am start -a android.intent.action.VIEW -d  網(wǎng)址
示例:am start -a android.intent.action.VIEW -d  http://www.baidu.com 
啟動(dòng)一個(gè)服務(wù)
命令:am startservice <服務(wù)名稱>
示例:am startservice -n com.android.music/com.android.music.MediaPlaybackService
execlp()函數(shù)

          execlp函數(shù)簡(jiǎn)單的來說就是C語(yǔ)言中執(zhí)行系統(tǒng)命令的函數(shù)
          execlp()會(huì)從PATH 環(huán)境變量所指的目錄中查找符合參數(shù)file 的文件名, 找到后便執(zhí)行該文件, 然后將第二個(gè)以后的參數(shù)當(dāng)做該文件的argv[0], argv[1], ..., 最后一個(gè)參數(shù)必須用空指針(NULL)作結(jié)束.
          android開發(fā)中,execlp函數(shù)對(duì)應(yīng)android的path路徑為system/bin/目錄下

調(diào)用格式:
execlp("am","am","start","--user","0","-a","android.intent.action.VIEW","-d","http://shouji.#/web/uninstall/uninstall.html",(char*)NULL);

===================================================================================================================

編寫代碼實(shí)現(xiàn)

1,Java層定義native方法


       在Java層定義一個(gè)native方法,提供在Java端和C端調(diào)用
public native void uninstall(String packageDir, int sdkVersion);

該方法需要傳遞應(yīng)用的安裝目錄和當(dāng)前設(shè)備的版本號(hào),在Java代碼中獲取,傳遞給C代碼處理。

2,使用javah命令生成方法簽名頭文件


/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_appuninstall_MainActivity */

#ifndef _Included_com_example_appuninstall_MainActivity
#define _Included_com_example_appuninstall_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_appuninstall_MainActivity
 * Method:    uninstall
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_example_appuninstall_MainActivity_uninstall
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

       方法簽名生成好之后,工程上右鍵 --> Android Tools --> Add Native Support,在彈出的對(duì)話框中輸入編輯的C/C++的文件名,確定之后,在工程的自動(dòng)生成的jni目錄下找到cpp后綴名的文件修改為.c后綴名的文件,因?yàn)楸景咐腔贑語(yǔ)言上實(shí)現(xiàn)的,然后同樣修改Android.mk文件中的LOCAL_SRC_FILES為.c的C文件,最后將上面生成好的.h方法簽名文件拷貝到j(luò)ni目錄下。

3,編寫C語(yǔ)言代碼


        正如上面原理分析的那樣,我們?cè)趯?shí)現(xiàn)這樣一個(gè)功能的時(shí)候用Java是無法實(shí)現(xiàn)的,只能在C中克隆出一個(gè)當(dāng)前App的子進(jìn)程,讓這個(gè)子進(jìn)程去監(jiān)聽?wèi)?yīng)用本身的卸載。那么實(shí)現(xiàn)這樣的功能我們需要哪些步驟呢?下面就是編寫代碼的思路:
1,將傳遞過來的java的包名轉(zhuǎn)為c的字符串
2,創(chuàng)建當(dāng)前進(jìn)程的克隆進(jìn)程
3,根據(jù)返回值的不同做不同的操作
4,在子進(jìn)程中監(jiān)視/data/data/包名這個(gè)目錄
5,目錄被刪除,說明被卸載,執(zhí)行打開用戶反饋的頁(yè)面
#include <stdio.h>
#include <jni.h>
#include <malloc.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include "com_example_appuninstall_MainActivity.h"
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

/**
 * 返回值 char* 這個(gè)代表char數(shù)組的首地址
 * Jstring2CStr 把java中的jstring的類型轉(zhuǎn)化成一個(gè)c語(yǔ)言中的char 字符串
 */
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
	char* rtn = NULL;
	jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
	jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一個(gè)java字符串 "GB2312"
	jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
			"(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
	jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
			strencode); // String .getByte("GB2312");
	jsize alen = (*env)->GetArrayLength(env, barr); // byte數(shù)組的長(zhǎng)度
	jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
	if (alen > 0) {
		rtn = (char*) malloc(alen + 1); //"\0"
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //
	return rtn;
}

JNIEXPORT void JNICALL Java_com_example_appuninstall_MainActivity_uninstall(
		JNIEnv * env, jobject obj, jstring packageDir, jint sdkVersion) {
	// 1,將傳遞過來的java的包名轉(zhuǎn)為c的字符串
	char * pd = Jstring2CStr(env, packageDir);

	// 2,創(chuàng)建當(dāng)前進(jìn)程的克隆進(jìn)程
	pid_t pid = fork();

	// 3,根據(jù)返回值的不同做不同的操作,<0,>0,=0
	if (pid < 0) {
		// 說明克隆進(jìn)程失敗
		LOGD("current crate process failure");
	} else if (pid > 0) {
		// 說明克隆進(jìn)程成功,而且該代碼運(yùn)行在父進(jìn)程中
		LOGD("crate process success,current parent pid = %d", pid);
	} else {
		// 說明克隆進(jìn)程成功,而且代碼運(yùn)行在子進(jìn)程中
		LOGD("crate process success,current child pid = %d", pid);

		// 4,在子進(jìn)程中監(jiān)視/data/data/包名這個(gè)目錄
		while (JNI_TRUE) {
			FILE* file = fopen(pd, "rt");

			if (file == NULL) {
				// 應(yīng)用被卸載了,通知系統(tǒng)打開用戶反饋的網(wǎng)頁(yè)
				LOGD("app uninstall,current sdkversion = %d", sdkVersion);
				if (sdkVersion >= 17) {
					// Android4.2系統(tǒng)之后支持多用戶操作,所以得指定用戶
					execlp("am", "am", "start", "--user", "0", "-a",
							"android.intent.action.VIEW", "-d",
							"http://www.baidu.com", (char*) NULL);
				} else {
					// Android4.2以前的版本無需指定用戶
					execlp("am", "am", "start", "-a",
							"android.intent.action.VIEW", "-d",
							"http://www.baidu.com", (char*) NULL);
				}
			} else {
				// 應(yīng)用沒有被卸載
				LOGD("app run normal");
			}
			sleep(1);
		}
	}

}
        上述代碼就如上述的步驟一樣,用C代碼實(shí)現(xiàn)了,首先注意的一點(diǎn)就是Android的版本問題,眾所周知,Android是基于Linux的非常優(yōu)秀的操作系統(tǒng),而且在Android4.2版本以后支持多用戶操作,但是這也給我們這個(gè)小小的項(xiàng)目中帶來了不便之處,因?yàn)樵诙嘤脩羟闆r下執(zhí)行am命令的時(shí)候強(qiáng)制指定一個(gè)用戶和一個(gè)編號(hào),在Android4.2之前的版本這些參數(shù)是沒有必要的,所以我們?cè)诰帉慍代碼的時(shí)候需要區(qū)別Android系統(tǒng)版本,分別執(zhí)行相應(yīng)的am命令,關(guān)于獲取Android系統(tǒng)版本可以在Java層實(shí)現(xiàn),然后將其作為參數(shù)傳遞給C代碼中,C代碼根據(jù)Android版本為判斷條件執(zhí)行am命令。
        注意:為了簡(jiǎn)便起見,我在C代碼監(jiān)視應(yīng)用是否被卸載的時(shí)候,使用了一個(gè)While(true)的死循環(huán),并且是每隔1毫秒執(zhí)行一次監(jiān)視檢測(cè),這樣寫的代碼是“不環(huán)保的”,想想這樣的結(jié)果是程序被不停的執(zhí)行,LOG被不停的打印,造成cpu計(jì)算資源浪費(fèi)和耗電是難免的。最好的解決方案是,使用Android給我們提供的FileObserve文件觀察者,F(xiàn)ileObserve使用到的是Linux系統(tǒng)下的inotify進(jìn)程,用來監(jiān)視文件目錄的變化的,本實(shí)例中如果需要優(yōu)化就需要使用這個(gè)API,但是需要的知識(shí)就更加多了,我現(xiàn)在為了簡(jiǎn)單的演示起見,暫時(shí)用了while(true)死循環(huán),關(guān)于后期的優(yōu)化版本,等我寫出來,再一起公布一下!

4,編譯.so動(dòng)態(tài)庫(kù)


       正如上篇博客寫的那樣,我們編寫好了C源碼之后,就需要使用ndk-build命令來編譯成.so文件了,具體編譯的過程也是非常簡(jiǎn)單的,在Eclipse中切換到C/C++編輯的手下,找到“小錘子”按鈕,點(diǎn)擊一下就開始編譯了,如果代碼沒有出現(xiàn)錯(cuò)誤的情況,編譯之后的結(jié)果是這樣的:
[img]http://img.blog.csdn.net/20150108165919244

5,編寫Java代碼,傳遞數(shù)據(jù) ,加載鏈接庫(kù)


        上面的工作做好了,剩下的就是在Java中加載這個(gè)鏈接庫(kù),和調(diào)用這個(gè)本地方法了。首先,要獲取本應(yīng)用安裝的目錄/data/data/包名,然后獲取當(dāng)前設(shè)備的版本號(hào),一起傳給本地方法中,最后調(diào)用這個(gè)方法。
public class MainActivity extends Activity {

	static {
		System.loadLibrary("uninstall");
	}

	public native void uninstall(String packageDir, int sdkVersion);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		String packageDir = "/data/data/" + getPackageName();
		int sdkVersion = android.os.Build.VERSION.SDK_INT;
		uninstall(packageDir, sdkVersion);
	}

}

6,測(cè)試


       好了,應(yīng)用是做完了,我們clean一下工程,然后啟動(dòng)一個(gè)基于ARM的模擬器,運(yùn)行這個(gè)程序,回到桌面,點(diǎn)擊應(yīng)用圖片——卸載掉這個(gè)應(yīng)用,看看效果:
[img]http://img.blog.csdn.net/20150108170609306
好了,大家看看效果吧,實(shí)際上打開的網(wǎng)頁(yè)應(yīng)該是用戶反饋調(diào)查頁(yè)面,由于我暫時(shí)沒有服務(wù)器,所以將網(wǎng)址定向到了百度首頁(yè)了,大家在開發(fā)的時(shí)候,可以將execlp函數(shù)里的參數(shù)網(wǎng)址改成自己的服務(wù)器網(wǎng)址,這樣就大功告成了。檢查一下Log日志的輸出:
[img]http://img.blog.csdn.net/20150108171022140
看到了,LOG輸入日志跟代碼流程是一致的,好了,源碼在下面的鏈接下,有興趣的朋友可以下載研究,歡迎你給我提出寶貴意見,大家一起學(xué)習(xí)一起進(jìn)步!
源碼請(qǐng)?jiān)谶@里下載

相關(guān)案例查看更多