360卸载监听


360卸载监听

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

结果展示
结果显示


文章作者: AheadSnail
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AheadSnail !
评论
  目录