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 */
}