{"id":21039513,"url":"https://github.com/charonchui/uninstallfeedback","last_synced_at":"2025-08-04T14:33:27.645Z","repository":{"id":75566772,"uuid":"48026562","full_name":"CharonChui/UninstallFeedback","owner":"CharonChui","description":"Show feedback page when the apk is uninstalled.","archived":false,"fork":false,"pushed_at":"2016-01-07T10:56:54.000Z","size":825,"stargazers_count":170,"open_issues_count":0,"forks_count":35,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-06-03T20:55:30.626Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CharonChui.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-12-15T07:23:27.000Z","updated_at":"2025-03-14T07:22:55.000Z","dependencies_parsed_at":"2023-03-17T07:30:26.026Z","dependency_job_id":null,"html_url":"https://github.com/CharonChui/UninstallFeedback","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/CharonChui/UninstallFeedback","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CharonChui%2FUninstallFeedback","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CharonChui%2FUninstallFeedback/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CharonChui%2FUninstallFeedback/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CharonChui%2FUninstallFeedback/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CharonChui","download_url":"https://codeload.github.com/CharonChui/UninstallFeedback/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CharonChui%2FUninstallFeedback/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268711493,"owners_count":24294786,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-04T02:00:09.867Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T13:42:02.890Z","updated_at":"2025-08-04T14:33:27.582Z","avatar_url":"https://github.com/CharonChui.png","language":"Makefile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UninstallFeedback\n\t\n最初记得是在360安全卫士中出现的，在手机上卸载他的应用之后浏览器就会弹出一个反馈页面，让用户进行反馈，感觉这种功能对于产品改进特别有帮助。\n但是仔细一想该怎么去实现却犯愁了，最开始想这也简单啊，不就是监听下自身被卸载就可以了，应该系统会有卸载的广播，可惜没有。甚至其他的一些\n方法也是不行的，因为你程序都被卸载了，你的代码怎么会执行呢？皮之不存，毛将焉附。那360是怎样实现的呢？说句真心话360做的产品还是非常有创新\n的，虽然有些时候他会损坏用户的利益，不过但从技术方面，着实让人信服。\n\n既然`Java`实现不了，那就得考虑下其他的了，自然最先想到的就是`JNI`了，可惜`C`的部分不懂，上网搜了很多资料和介绍，找得到可以通过一下方式实现:   \n- 通过`c`中的`fork`方法来复制一个子进程。复制出来的子进程在父进程被销毁后，仍然可以存在。\n    `pid_t fpid = fork()`被调用前，就一个进程执行该段代码，这条语句执行之后，就会有两个进程执行该代码，两个进程执行没有固定先后顺序，只要看\n\t系统调度策略，`fork()`函数的特别之处在于调用一次会返回两次结果：   \n\t    - 返回值大于0，当前是父进程。\n\t\t- 返回0，当前是子进程。\n\t\t- 返回小于0的负值。(出错了，可能是内存不足或者是进程数已经达到系统最大值)\n\n- `fork`出子进程后让子进程一直去监听`/data/data/packageName`是否存在，如果不存在了，那就说明程序被卸载了，但是这样一直去轮训判断肯定会浪费\n    系统资源的，当然也会更加费电，对用户来讲肯定是有损害的。所以这种技术最好也不好用，不然大家的手机以后还能了得。万恶的产品。\n\n- 得到程序被卸载之后弹出浏览器打开指定反馈页面。这就要用到`am`命令了，最早知道这个命令是在开发`TV`版的时候，遥控器找不到了，程序安装后无法\n   打开了，用该命令就不怕了，哈哈。但是在`c`中怎么执行`am`命令呢？这就要用到`execlp()`函数，该函数就是`c`中执行系统命令的函数。\n   \n\n好了，主要的内容上面都分析完了，下面上代码。\n\n- `Java`层定义`natvie`方法。   \n```java\npublic class MainActivity extends Activity {\n\tprivate static final String TAG = \"@@@\";\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.activity_main);\n\t\tString packageDir = \"/data/data/\" + getPackageName();\n\t\tinitUninstallFeedback(packageDir, Build.VERSION.SDK_INT);\n\t}\n\n\tprivate native void initUninstallFeedback(String packagePath, int sdkVersion);\n\n\tstatic {\n\t\tSystem.loadLibrary(\"uninstall_feedback\");\n\t}\n}\n```\n\n- 创建jni目录，增加`Android.mk`以及`uninstall_feedback.c`文件。\n`Android.mk`的内容:\n\n```c\nLOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE    := uninstall_feedback\nLOCAL_SRC_FILES := uninstall_feedback.c\n\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/include\nLOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog\n\ninclude $(BUILD_SHARED_LIBRARY)\n```\n\n`uninstall_feedback.c`的实现: \n\n```c\n/**\n * 将Java中的String转换成c中的char字符串\n */\nchar* Jstring2CStr(JNIEnv* env, jstring jstr) {\n\tchar* rtn = NULL;\n\tjclass clsstring = (*env)-\u003eFindClass(env, \"java/lang/String\"); //String\n\tjstring strencode = (*env)-\u003eNewStringUTF(env, \"GB2312\"); // 得到一个java字符串 \"GB2312\"\n\tjmethodID mid = (*env)-\u003eGetMethodID(env, clsstring, \"getBytes\",\n\t\t\t\"(Ljava/lang/String;)[B\"); //[ String.getBytes(\"gb2312\");\n\tjbyteArray barr = (jbyteArray)(*env)-\u003eCallObjectMethod(env, jstr, mid,\n\t\t\tstrencode); // String .getByte(\"GB2312\");\n\tjsize alen = (*env)-\u003eGetArrayLength(env, barr); // byte数组的长度\n\tjbyte* ba = (*env)-\u003eGetByteArrayElements(env, barr, JNI_FALSE);\n\tif (alen \u003e 0) {\n\t\trtn = (char*) malloc(alen + 1); //\"\"\n\t\tmemcpy(rtn, ba, alen);\n\t\trtn[alen] = 0;\n\t}\n\t(*env)-\u003eReleaseByteArrayElements(env, barr, ba, 0); //\n\treturn rtn;\n}\n\nvoid Java_com_charon_uninstallfeedback_MainActivity_initUninstallFeedback(\n\t\tJNIEnv* env, jobject thiz, jstring packageDir, jint sdkVersion) {\n\n\tchar * pd = Jstring2CStr(env, packageDir);\n\n\t//fork子进程，以执行轮询任务\n\tpid_t pid = fork();\n\n\tif (pid \u003c 0) {\n\t\t// fork失败了\n\t} else if (pid == 0) {\n\t\t// 可以一直采用一直判断文件是否存在的方式去判断，但是这样效率稍低，下面使用监听的方式，死循环，每个一秒判断一次，这样太浪费资源了。\n\t\tint check = 1;\n\t\twhile (check) {\n\t\t\tFILE* file = fopen(pd, \"rt\");\n\t\t\tif (file == NULL) {\n\t\t\t\tif (sdkVersion \u003e= 17) {\n\t\t\t\t\t// Android4.2系统之后支持多用户操作，所以得指定用户\n\t\t\t\t\texeclp(\"am\", \"am\", \"start\", \"--user\", \"0\", \"-a\",\n\t\t\t\t\t\t\t\"android.intent.action.VIEW\", \"-d\",\n\t\t\t\t\t\t\t\"http://shouji.360.cn/web/uninstall/uninstall.html\",\n\t\t\t\t\t\t\t(char*) NULL);\n\t\t\t\t} else {\n\t\t\t\t\t// Android4.2以前的版本无需指定用户\n\t\t\t\t\texeclp(\"am\", \"am\", \"start\", \"-a\",\n\t\t\t\t\t\t\"android.intent.action.VIEW\", \"-d\",\n\t\t\t\t\t\t\t\"http://shouji.360.cn/web/uninstall/uninstall.html\",\n\t\t\t\t\t\t\t(char*) NULL);\n\t\t\t\t}\n\t\t\t\tcheck = 0;\n\t\t\t} else {\n\t\t\t}\n\t\t\tsleep(1);\n\t\t}\n\t} else {\n\t}\n}\n\n```\n\n- 编译so文件。`Windows`下要用`cygwin`来操作。                 \n\n上面的介绍是在`Eclipse`中进行的，用`ndk-build`命令来编译`so`。                 \n有关`JNI`的使用可以参考[JNI基础](https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%9F%BA%E7%A1%80/JNI%E5%9F%BA%E7%A1%80.md)                                  \n使用`Android Studio`开发`JNI`的时候出现了插曲，急着赶时间就改成用`eclipse`来写。      \n有关`Studio`下的`NDK`开发可以参考[AndroidStudio中进行ndk开发](https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/AndroidStudio%E4%B8%AD%E8%BF%9B%E8%A1%8Cndk%E5%BC%80%E5%8F%91.md)\n\n最后发现这种轮询判断文件夹是否存在的方式问题太多，后来查了写资料，根据大神[热气球](http://www.cnblogs.com/zealotrouge/p/3182617.html)的方法进行了修改。              \n感谢大神[热气球](http://www.cnblogs.com/zealotrouge/p/3182617.html)的无私奉献。\n                  \n基于该想法也能实现服务的常驻内存。暂时还没有时间去做。这种做法从用户角度来讲，还是比较恶劣的，大家尽量不要用到实际项目中。\n\n---\n\n- 邮箱 ：charon.chui@gmail.com  \n- Good Luck! \n\n \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharonchui%2Funinstallfeedback","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcharonchui%2Funinstallfeedback","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharonchui%2Funinstallfeedback/lists"}