Repository: lzyickobe/UnInstallDemo Branch: master Commit: 12ca0e74e339 Files: 14 Total size: 20.0 KB Directory structure: gitextract_6sfa6kaj/ ├── .gitattributes ├── .gitignore ├── .project ├── AndroidManifest.xml ├── README.md ├── jni/ │ ├── Android.mk │ └── UninstalledObserver.c ├── libs/ │ └── android-support-v4.jar ├── proguard-project.txt ├── project.properties ├── res/ │ ├── layout/ │ │ └── activity_main.xml │ └── values/ │ ├── dimens.xml │ └── strings.xml └── src/ └── com/ └── lzyblog/ └── uninstalldemo/ └── UninstalledObserverActivity.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp *.sln merge=union *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # ========================= # Operating System Files # ========================= # OSX # ========================= .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ================================================ FILE: .project ================================================ UnInstallDemo com.android.ide.eclipse.adt.ResourceManagerBuilder com.android.ide.eclipse.adt.PreCompilerBuilder org.eclipse.jdt.core.javabuilder com.android.ide.eclipse.adt.ApkBuilder org.eclipse.ui.externaltools.ExternalToolBuilder auto,full,incremental, LaunchConfigHandle <project>/.externalToolBuilders/New_NDK.launch com.android.ide.eclipse.adt.AndroidNature org.eclipse.jdt.core.javanature ================================================ FILE: AndroidManifest.xml ================================================ ================================================ FILE: README.md ================================================ ## Android应用监听自己是否被卸载,卸载后弹出反馈网页 ### 在前人的基础上有这些解决方案 1. 监听卸载广播,只能监听到别人卸载。自己被卸载的时候,早就收不到广播了。 2. 监听log。这样听起来很靠谱,能稳定监听到,但是发送操作不靠谱。 3. 监听/data/data/。当Android卸载应用的时候,会先删除这里的文件。可以轮询监听,可以优化成unix文件监听方式,,这样只用等待文件监听服务的回调。 ### 采用了第3种解决办法,并对其进行了优化: #### 问题: 监听/data/data/这个目录,还存在以下几个问题: 1. 清除数据、插拔USB线、覆盖安装等操作引起程序误判卸载。 2. 重复监听的问题。 3. 用户将已在Internal SD卡安装好的应用移动到external SD卡,导致监听不正常。 #### 原因: 1. 由于inotify_add_watch(fileDescriptor, path, IN_DELETE)这个函数会监听path目录下所有文件的删除操作导致。 2. 重复调用JNI的init方法 3. 暂时未修复 #### 解决方法: 1. 监听不应该针对整个文件夹,而是某个文件。 2. 重复监听的问题,都可以通过加文件锁来防止 详细方案可参考我的博文:[Android监听自己是否被卸载](http://lzyblog.com/2015/01/09/Android%E5%BA%94%E7%94%A8%E7%9B%91%E5%90%AC%E8%87%AA%E5%B7%B1%E6%98%AF%E5%90%A6%E8%A2%AB%E5%8D%B8%E8%BD%BD%EF%BC%8C%E5%81%9A%E5%8F%8D%E9%A6%88%E7%BB%9F%E8%AE%A1/) 参考自: https://github.com/sevenler/Uninstall_Statics http://www.cnblogs.com/zealotrouge/p/3157126.html http://www.cnblogs.com/zealotrouge/p/3159772.html http://www.cnblogs.com/zealotrouge/p/3182617.html ================================================ FILE: jni/Android.mk ================================================ # Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := uninstalled_observer LOCAL_SRC_FILES := UninstalledObserver.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog include $(BUILD_SHARED_LIBRARY) ================================================ FILE: jni/UninstalledObserver.c ================================================ /* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * NOTICE:cļҪĶֱʶ TODO1 TODO2 */ #include #include #include #include #include #include #include #include #include /* 궨begin */ //0 #define MEM_ZERO(pDest, destSize) memset(pDest, 0, destSize) //LOG궨 #define LOG_INFO(tag, msg) __android_log_write(ANDROID_LOG_INFO, tag, msg) #define LOG_DEBUG(tag, msg) __android_log_write(ANDROID_LOG_DEBUG, tag, msg) #define LOG_WARN(tag, msg) __android_log_write(ANDROID_LOG_WARN, tag, msg) #define LOG_ERROR(tag, msg) __android_log_write(ANDROID_LOG_ERROR, tag, msg) /* ȫֱbegin */ static char TAG[] = "UninstalledObserverActivity.init"; static jboolean isCopy = JNI_TRUE; //--TODO1--------------------ҪijԼapkİ------------------------------------ static const char APP_DIR[] = "/data/data/com.lzyblog.uninstalldemo"; static const char APP_FILES_DIR[] = "/data/data/com.lzyblog.uninstalldemo/files"; static const char APP_OBSERVED_FILE[] = "/data/data/com.lzyblog.uninstalldemo/files/observedFile"; static const char APP_LOCK_FILE[] = "/data/data/com.lzyblog.uninstalldemo/files/lockFile"; /* ȫֱ */ /*-------TODO2----------------------- * Class: ҪijԼapp * Method: init * Signature: ()V * return: ӽpid */ JNIEXPORT int JNICALL Java_com_lzyblog_uninstalldemo_UninstalledObserverActivity_init(JNIEnv *env, jobject obj, jstring userSerial, jstring website) { jstring tag = (*env)->NewStringUTF(env, TAG); char* websiteStr=(char*) (*env)->GetStringUTFChars(env, website, NULL); LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "init observer"), &isCopy)); // forkӽִ̣ѯ pid_t pid = fork(); if (pid < 0) { LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork failed !!!"), &isCopy)); exit(1); } else if (pid == 0) { LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork Success !!!"), &isCopy)); // ļļвڣ FILE *p_filesDir = fopen(APP_FILES_DIR, "r"); if (p_filesDir == NULL) { int filesDirRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH); if (filesDirRet == -1) { LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "mkdir failed !!!"), &isCopy)); exit(1); } LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "mkdir Success !!!"), &isCopy)); } LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "app dir is exist !!!"), &isCopy)); // ļڣļ FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r"); if (p_observedFile == NULL) { LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "the observed file is not existed!!!"), &isCopy)); p_observedFile = fopen(APP_OBSERVED_FILE, "w"); if (p_observedFile == NULL) { LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "the observed file create falied!!!"), &isCopy)); } } fclose(p_observedFile); // ļͨ״ֻ̬֤һжؼ int lockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY); if (lockFileDescriptor == -1) { lockFileDescriptor = open(APP_LOCK_FILE, O_CREAT); } int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB); if (lockRet == -1) { LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "observed by another process"), &isCopy)); exit(0); } LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "observed by child process"), &isCopy)); // ռ䣬Աȡevent void *p_buf = malloc(sizeof(struct inotify_event)); if (p_buf == NULL) { LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &isCopy)); exit(1); } // ռ䣬Աӡmask int maskStrLength = 7 + 10 + 1;// mask=0xռ7ֽڣ32λΪ10λתΪַռ10ֽڣ'\0'ռ1ֽ char *p_maskStr = malloc(maskStrLength); if (p_maskStr == NULL) { free(p_buf); LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &isCopy)); exit(1); } // ʼ LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "start observe"), &isCopy)); // ʼ int fileDescriptor = inotify_init(); if (fileDescriptor < 0) { free(p_buf); free(p_maskStr); LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_init failed !!!"), &isCopy)); exit(1); } // ӱļб int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS); if (watchDescriptor < 0) { free(p_buf); free(p_maskStr); LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &isCopy)); exit(1); } while(1) { // read size_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event)); // ӡmask snprintf(p_maskStr, maskStrLength, "mask=0x%x\0", ((struct inotify_event *) p_buf)->mask); LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, p_maskStr), &isCopy)); if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask) { LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "the observer file is deleted"), &isCopy)); inotify_rm_watch(fileDescriptor, watchDescriptor); break; //ʵжģȷĸжأĸݣ /* FILE *p_appDir = fopen(APP_DIR, "r"); // ȷж if (p_appDir == NULL) { LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "app is uninstall"), &isCopy)); inotify_rm_watch(fileDescriptor, watchDescriptor); break; } // δжأûִ"" else { LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "app not uninstall , the data is empty"), &isCopy)); fclose(p_appDir); // ´ļ¼ FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "w"); fclose(p_observedFile); int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS); if (watchDescriptor < 0) { free(p_buf); free(p_maskStr); LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &isCopy)); exit(1); } } */ } } // ͷԴ free(p_buf); free(p_maskStr); // ֹͣ LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "stop observe"), &isCopy)); if (userSerial == NULL) { // ִam start -a android.intent.action.VIEW -d $(url) execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", websiteStr, (char *)NULL); } else { // ִam start --user userSerial -a android.intent.action.VIEW -d $(url) execlp("am", "am", "start", "--user", (*env)->GetStringUTFChars(env, userSerial, &isCopy), "-a", "android.intent.action.VIEW", "-d", websiteStr, (char *)NULL); } // ִʧlog LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "exec AM command failed !!!"), &isCopy)); (*env)->ReleaseStringUTFChars(env, website, websiteStr); } else { // ֱ˳ʹӽ̱initԱӽ̽ͬʱӽpid (*env)->ReleaseStringUTFChars(env, website, websiteStr); return pid; } } ================================================ FILE: proguard-project.txt ================================================ # To enable ProGuard in your project, edit project.properties # to define the proguard.config property as described in that file. # # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the ProGuard # include property in project.properties. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: project.properties ================================================ # This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. target=android-19 ================================================ FILE: res/layout/activity_main.xml ================================================ ================================================ FILE: res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: res/values/strings.xml ================================================ UnInstallDemo Hello world! Settings ================================================ FILE: src/com/lzyblog/uninstalldemo/UninstalledObserverActivity.java ================================================ package com.lzyblog.uninstalldemo; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.util.Log; /** * @author liuzhiyong תpengyiming * @note ӦǷжأж򵯳жط * @note API17û֧֣ԭ4.2߰汾ִʱȱuserSerialش޸ * @note ˴δ޸ᵽһЩbugݡUSBߡǰװȲжء * @note κظ⣬ͨļֹpsȡؽ˽ķҪúܶࡣ * @note װSDжؼȻû⣬ûInternal SDװõӦƶexternal * SD.c161δfilesļкļӦûbug붼УҪ޸bugɡ * @note ͵ַ:http://lzyblog.com */ public class UninstalledObserverActivity extends Activity { /* ݶbegin */ private static final String TAG = "UninstalledObserverActivity"; //վ private static final String WEBSITE = "http://lzyblog.com"; // pid private int mObserverProcessPid = -1; /* ݶend */ /* static */ // ʼ private native int init(String userSerial, String webSite); static { Log.d(TAG, "load lib --> uninstalled_observer"); System.loadLibrary("uninstalled_observer"); } /* static */ /* begin */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); createFile(); // API levelС17ҪȡuserSerialNumber if (Build.VERSION.SDK_INT < 17) { mObserverProcessPid = init(null, WEBSITE); } // ҪȡuserSerialNumber else { mObserverProcessPid = init(getUserSerial(), WEBSITE); } } private void createFile() { File file = new File("/data/data/com.lzyblog.uninstalldemo/files/observedFile"); if (!file.exists()) { try { File dir = new File("/data/data/com.lzyblog.uninstalldemo/files"); if (!dir.exists()) { if (dir.mkdir()) { Log.e(TAG, "filesĿ¼ɹ"); } else { Log.e(TAG, "filesĿ¼ʧ"); return; } } if (file.createNewFile()) { Log.e(TAG, "observedFileɹ"); return; } Log.e(TAG, "observedFileʧ"); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "observedFileʧ"); } } else { Log.e(TAG, "observedFile"); } } @Override protected void onDestroy() { super.onDestroy(); // ʾ룬ڽ // if (mObserverProcessPid > 0) // { // android.os.Process.killProcess(mObserverProcessPid); // } } // targetSdkVersion17ֻͨȡ private String getUserSerial() { Object userManager = getSystemService("user"); if (userManager == null) { Log.e(TAG, "userManager not exsit !!!"); return null; } try { Method myUserHandleMethod = android.os.Process.class.getMethod( "myUserHandle", (Class[]) null); Object myUserHandle = myUserHandleMethod.invoke( android.os.Process.class, (Object[]) null); Method getSerialNumberForUser = userManager.getClass().getMethod( "getSerialNumberForUser", myUserHandle.getClass()); long userSerial = (Long) getSerialNumberForUser.invoke(userManager, myUserHandle); return String.valueOf(userSerial); } catch (NoSuchMethodException e) { Log.e(TAG, "", e); } catch (IllegalArgumentException e) { Log.e(TAG, "", e); } catch (IllegalAccessException e) { Log.e(TAG, "", e); } catch (InvocationTargetException e) { Log.e(TAG, "", e); } return null; } /* end */ }