360卸载监听,这边对应的为android源码6.0
针对于Android的系统,我们可以试想有一下策略
1,监听系统卸载广播
只能监听到其他应用的卸载广播,无法监听到自己是否被卸载,主要是依靠内存 --->>监听别人 可以通过这个广播来监听 ACTION_PACKAGE_REMOVED
2.通过监听系统log,一般的情况下,下载软件会有系统的log发出 (正在被安装的包程序不能接收到这个广播)
3.通过java线程,轮询 监听 监听/data/data/{package-name}目录是否存在
4.C进程 监听/data/data/{package-name}目录是否存在 跳转到网页
5.静默安装另外的apk 监听自己是否被卸载 可以 (root)
从前四种方案可以看到,单纯的Java层代码是无法监听自身卸载的。既然Java层无法实现,我们试着使用C语言在底层实现。
借助Linux进程fork出来的C进程在应用被卸载后不会被销毁,监听/data/data/{package-name}目录是否存在,如果不存在,就证明应用被卸载了。
java中可以通过FileObserver来监听到文件的变化,但是FileObserver只能监听到别人的文件,不可以监听到自己的文件,(应用都被卸载了,内存里面的内容也不会存在了)
java中可以通过FileObserver来监听到文件的变化,android源码6.0
第一个文件代表要关注,监听到文件的路径,第二个参数为监听的方式,这里只是监听删除的方法 DELETE
FileObserver observer = new FileObserver("/data/data/",FileObserver.DELETE)
{
@Override
public void onEvent(int i, String s)
{
};
observer.startWatching();
}
这个类一上来就有这样的静态代码库,创建了一个线程
static {
s_observerThread = new ObserverThread();
s_observerThread.start();
}
线程在创建的时候,调用了init()方法
public ObserverThread() {
super("FileObserver");
m_fd = init();
}
private native int init(); 观看源码的实现为,对应的文件的路径在/frameworks/base/core/jni/android_util_FileObserver.cpp
下面采用的动态注册的方式
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{ "init", "()I", (void*)android_os_fileobserver_init },
{ "observe", "(I)V", (void*)android_os_fileobserver_observe },
{ "startWatching", "(ILjava/lang/String;I)I", (void*)android_os_fileobserver_startWatching },
{ "stopWatching", "(II)V", (void*)android_os_fileobserver_stopWatching }
};
android_os_fileobserver_init返回一个文件的句柄,文件的句柄在linux里面是很重要的,通过文件的句柄就可以操作文件
static jint android_os_fileobserver_init(JNIEnv* env, jobject object)
{
#if defined(__linux__)
return (jint)inotify_init(); 返回一个文件的句柄
#else
return -1;
#endif
}
当客户端调用下面代码的时候
observer.startWatching();
public void startWatching() {
if (m_descriptor < 0) {
m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
}
}
public int startWatching(String path, int mask, FileObserver observer) {
int wfd = startWatching(m_fd, path, mask);
Integer i = new Integer(wfd);
if (wfd >= 0) {
synchronized (m_observers) {
m_observers.put(i, new WeakReference(observer));
}
}
return i;
}
int wfd = startWatching(m_fd, path, mask); startWatching为native的原型
private native int startWatching(int fd, String path, int mask);
static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask)
{
int res = -1;
#if defined(__linux__)
if (fd >= 0)
{
const char* path = env->GetStringUTFChars(pathString, NULL);
//inotify_add_watch 是linux函数,可以用来监听文件,第一个参数为文件的句柄,第二个参数为要监听的文件的路径,第三个为监听这个文件的方式,比如删除等
res = inotify_add_watch(fd, path, mask);
env->ReleaseStringUTFChars(pathString, path);
}
#endif
return res;
}
s_observerThread.start();就会执行run方法
public void run() {
observe(m_fd);
}
observe(m_fd)为native方法,下面为函数的原型
private native void observe(int fd);
static void android_os_fileobserver_observe(JNIEnv* env, jobject object, jint fd)
{
#if defined(__linux__)
char event_buf[512];
struct inotify_event* event;
int event_pos = 0;
这里要注意的是read是阻塞的函数,就是如果读不到内容的时候,就会一值阻塞
int num_bytes = read(fd, event_buf, sizeof(event_buf));
......
}
代码的实现
static const char APP_DIR[] = "/data/data/com.david.a360unistall";
static const char observerFile[] = "/data/data/com.david.a360unistall/observedFile";
extern "C"
JNIEXPORT void JNICALL
Java_com_david_a360unistall_MainActivity_init(JNIEnv *env, jobject instance,jint sdkVersion ) {
const char *path = env->GetStringUTFChars(path_, 0);
pid_t pid=fork();
if (pid < 0) {
LOGD("fork失败");
//系统fork()
} else if (pid > 0) {
LOGD("父进程");
} else {
LOGD("子进程");
// 初始化 inotify
int fileDescriple = inotify_init();
int watch=inotify_add_watch(fileDescriple, path, IN_DELETE_SELF);
void *p_buf=malloc(sizeof(struct inotify_event));
// 阻塞式函数 anr,所以我们这里fork 一个进程用来监听,要不然会阻塞当前的进程
size_t readBytes=read(fileDescriple,p_buf, sizeof(struct inotify_event));
inotify_rm_watch(fileDescriple, watch);
LOGD("跳转网页");
//铁了心要删除app sdk 17 am 设置到环境变量 多用户的操作 ,注意这里17版本有区别
if (sdk < 17) {
execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d",
"http://www.baidu.com",NULL);
} else{
execlp("am", "am", "start","--user","0","-a", "android.intent.action.VIEW", "-d",
"http://www.baidu.com",NULL);
}
}
env->ReleaseStringUTFChars(path_, path);
}
execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d",
"http://www.dongnaoedu.com", NULL);
am也是一个可以执行的程序,路径在android-6.0.0_r1\android-6.0.0_r1\frameworks\base\cmds\am目录,里面有一个am.java文件,main的函数为
public static void main(String[] args) {
(new Am()).run(args);
}
所以传递的最后一个参数要为NULL,用来标识字符串
am start -n com.yuanhh.app/.MainActivity
am 不需要弹选择框
AM的参数使用
● -a <ACTION>: 指定Intent action, 实现原理Intent.setAction();
● -n <COMPONENT>: 指定组件名,格式为{包名}/.{主Activity名},实现原理Intent.setComponent();
● -d <DATA_URI>: 指定Intent data URI
● -t <MIME_TYPE>: 指定Intent MIME Type
● -c <CATEGORY> [-c <CATEGORY>] ...]:指定Intent category,实现原理Intent.addCategory()
● -p <PACKAGE>: 指定包名,实现原理Intent.setPackage();
● -f <FLAGS>: 添加flags,实现原理Intent.setFlags(int ),紧接着的参数必须是int型;
基本类型
参数-e/-es-esn-ez-ei-el-ef-eu-ecn类型String(String)nullbooleanintlongfloaturicomponent
比如参数es是Extra String首字母简称,实例:
intent.putExtra("website","website gityuan.com")
am start -n com.dongnao.app/.MainActivity -es website gityuan.com
结果展示