Android应用被卸载后,自动使用 浏览器打开指定连接(或编写C代码执行其他操作)

简介:

本文,提供“Android应用被卸载后,自动使用 浏览器打开指定连接”的方法。
原理:在安卓程序中某处,基于JNI调用C代码开启一个子进程监控应用在系统中的文件目录,一旦应用被卸载,该目录将会被系统删除,此时触发子进程执行相关代码(本例调用浏览器执行打开一个连接)
1、在安卓项目下创建jni目录
2、在jni目录下创建文件observer.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.
 *
 */
#include <string.h>
#include <jni.h>

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <android/log.h>
#include <unistd.h>
#include <sys/inotify.h>

/* 宏定义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 c_TAG[] = "OnAppUninstall";
static jboolean b_IS_COPY = JNI_TRUE;

/* native方法定义 */
JNIEXPORT jstring JNICALL Java_com_zgy_catchuninstallself_UninstallObserver_startWork(
        JNIEnv* env, jobject thiz, jstring dirStr, jstring data_packagedir, jstring activity,
        jstring action, jstring action_data, jstring userSerial) {
    jstring tag = (*env)->NewStringUTF(env, c_TAG);

    LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
            (*env)->GetStringUTFChars(env,
                    (*env)->NewStringUTF(env, "start------------------"),
                    &b_IS_COPY));
    //初始化log
    LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
            (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "init OK"),
                    &b_IS_COPY));

    //fork子进程,以执行轮询任务
    pid_t pid = fork();
    char showpid[10];
    sprintf(showpid, "%d", pid);
    LOG_INFO((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
            (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, showpid),
                    &b_IS_COPY));

    if (pid < 0) {
        //出错log
        LOG_ERROR((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "fork failed !!!"),
                        &b_IS_COPY));
    } else if (pid == 0) {
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "childen process"),
                        &b_IS_COPY));
        int fileDescriptor = inotify_init();
        if (fileDescriptor < 0) {
            //初始化文件监听器失败
            LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                    (*env)->GetStringUTFChars(env,
                            (*env)->NewStringUTF(env,
                                    "inotify_init failed !!!"), &b_IS_COPY));
            exit(1);
        }
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "will creat a lockfile"),
                        &b_IS_COPY));
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env, dirStr, &b_IS_COPY));
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "creat a new process"),
                        &b_IS_COPY));
        //子进程注册"/data/data/{package}"目录监听器
        int watchDescriptor;
        watchDescriptor = inotify_add_watch(fileDescriptor,
                (*env)->GetStringUTFChars(env, dirStr, &b_IS_COPY), IN_DELETE);
        if (watchDescriptor < 0) {
            LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                    (*env)->GetStringUTFChars(env,
                            (*env)->NewStringUTF(env,
                                    "inotify_add_watch failed !!!"),
                            &b_IS_COPY));
            exit(1);
        }
        //分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
        void *p_buf = malloc(sizeof(struct inotify_event));
        if (p_buf == NULL) {
            LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                    (*env)->GetStringUTFChars(env,
                            (*env)->NewStringUTF(env, "malloc failed !!!"),
                            &b_IS_COPY));
            exit(1);
        }
        //开始监听
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "start observer"),
                        &b_IS_COPY));
        size_t readBytes = read(fileDescriptor, p_buf,
                sizeof(struct inotify_event));
        //等待5秒
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "sleep 2 seconds"),
                        &b_IS_COPY));
        sleep(2);
        //如果是覆盖安装跳过
        FILE *p_appDir = fopen(data_packagedir, "r");
        if (p_appDir != NULL) {
            LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                    (*env)->GetStringUTFChars(env,
                            (*env)->NewStringUTF(env, "reinstall"),
                            &b_IS_COPY));
            exit(1);
        }
        //read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器
        free(p_buf);
        inotify_rm_watch(fileDescriptor, IN_DELETE);
        //目录不存在log
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "uninstalled"), &b_IS_COPY));
        //执行命令am start -a android.intent.action.VIEW -d http://ww.baidu.com
        // execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://ww.baidu.com", (char *)NULL);
        //4.2以上的系统由于用户权限管理更严格,需要加上 --user 0
        if (userSerial == NULL) {
            // 执行命令am start -a android.intent.action.VIEW -d $(url)
            execlp("am", "am", "start", "-a", "android.intent.action.VIEW",
                    "-d", (*env)->GetStringUTFChars(env, action_data, &b_IS_COPY),
                    (char *) NULL);
        } else {
            if (activity == NULL
                    || (*env)->GetStringUTFLength(env, activity) < 1) {
                execlp("am", "am", "start", "--user",
                        (*env)->GetStringUTFChars(env, userSerial, &b_IS_COPY),
                        "-a",
                        (*env)->GetStringUTFChars(env, action, &b_IS_COPY),
                        "-d", (*env)->GetStringUTFChars(env, action_data, &b_IS_COPY),
                        (char *) NULL);
            } else {
                if (action == NULL
                        || (*env)->GetStringUTFLength(env, action) < 1) {
                    if (action_data == NULL
                            || (*env)->GetStringUTFLength(env, action_data) < 1) {
                        execlp("am", "am", "start", "--user",
                                (*env)->GetStringUTFChars(env, userSerial,
                                        &b_IS_COPY), "-n",
                                (*env)->GetStringUTFChars(env, activity,
                                        &b_IS_COPY), (char *) NULL);
                    } else {
                        execlp("am", "am", "start", "--user",
                                (*env)->GetStringUTFChars(env, userSerial,
                                        &b_IS_COPY), "-n",
                                (*env)->GetStringUTFChars(env, activity,
                                        &b_IS_COPY), "-d",
                                (*env)->GetStringUTFChars(env, action_data,
                                        &b_IS_COPY), (char *) NULL);
                    }
                } else {
                    if (action_data == NULL
                            || (*env)->GetStringUTFLength(env, action_data) < 1) {
                        execlp("am", "am", "start", "--user",
                                (*env)->GetStringUTFChars(env, userSerial,
                                        &b_IS_COPY), "-n",
                                (*env)->GetStringUTFChars(env, activity,
                                        &b_IS_COPY), "-a",
                                (*env)->GetStringUTFChars(env, action,
                                        &b_IS_COPY), (char *) NULL);
                    } else {
                        execlp("am", "am", "start", "--user",
                                (*env)->GetStringUTFChars(env, userSerial,
                                        &b_IS_COPY), "-n",
                                (*env)->GetStringUTFChars(env, activity,
                                        &b_IS_COPY), "-a",
                                (*env)->GetStringUTFChars(env, action,
                                        &b_IS_COPY), "-d",
                                (*env)->GetStringUTFChars(env, action_data,
                                        &b_IS_COPY), (char *) NULL);
                    }
                }
            }
        }
        //扩展:可以执行其他shell命令,am(即activity manager),可以打开某程序、服务,broadcast intent,等等

    } else {
        //父进程直接退出,使子进程被init进程领养,以避免子进程僵死
        LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
                (*env)->GetStringUTFChars(env,
                        (*env)->NewStringUTF(env, "fork pid > 0"), &b_IS_COPY));
    }
    LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
            (*env)->GetStringUTFChars(env,
                    (*env)->NewStringUTF(env, "end------------------"),
                    &b_IS_COPY));

    return (*env)->NewStringUTF(env, showpid);
}

3、在jni目录下创建文件Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:=observer
LOCAL_SRC_FILES:=observer.c
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)

4、在创建natvie方法,并在Android中调用JNI方法

package com.zgy.catchuninstallself;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.util.Log;

/**
 * 应用程序卸载监控,入口调用方法:openUrlWhenUninstall(……)
 *
 * @author SHANHY(365384722@QQ.COM)
 * @date   2015年12月15日
 */
public class UninstallObserver {

    // path:data/data/[packageNmae]/lib 
    // url:跳转的页面,需要http://或https://开头
    /**
     * Native 方法
     * 
     * @param dirStr    要监听的目录,传值:/data/data/{packageNmae}/lib
     * @param activity  Activity对象
     * @param action    android.intent.action.VIEW
     * @param actionData    要在浏览器中打开的URL
     * @param userSerial
     * @return 
     * @author SHANHY
     * @date   2015年12月15日
     */
    private static native String startWork(String dirStr,String dataPackageDir, String activity, String action, String actionData, String userSerial);

    static {
        System.loadLibrary("observer");
    }

    private static SharedPreferences sharedPreferences;
    private static SharedPreferences.Editor editor;

    /**
     * 在需要开启子进程监听的地方调用该方法
     * 
     * @param context
     * @param openUrl
     * @author SHANHY
     * @date   2015年12月15日
     */
    public static void openUrlWhenUninstall(Context context, String openUrl) {
        sharedPreferences = context.getSharedPreferences("process", Context.MODE_PRIVATE);
        editor = sharedPreferences.edit();
        String pid = sharedPreferences.getString("pid", "0");
        if (checkChildProcess(context, pid)) {
            Log.i("OnAppUninstall", "已经开启过守护进程");
            return;
        }
        Log.i("OnAppUninstall", "执行开启过守护进程");
        String dataPackageDir = context.getApplicationInfo().dataDir;
        String dirStr = context.getApplicationInfo().dataDir + "/lib";// 监听lib目录是因为:lib目录在进行应用清理的时候不会被清理
        String activity = null;
        if (checkInstall(context, "com.android.browser")) {
            activity = "com.android.browser/com.android.browser.BrowserActivity";
        }
        String action = "android.intent.action.VIEW";
        String gpid;
        if (Build.VERSION.SDK_INT < 17) {
            gpid = startWork(dirStr, dataPackageDir, activity, action, openUrl, null);
        } else {//4.2以上的系统由于用户权限管理更严格,调用API有所区别
            gpid = startWork(dirStr, dataPackageDir, activity, action, openUrl, getUserSerial(context));
        }
        if (gpid != null && !gpid.equals("") && !gpid.equals("0")) {
            Log.i("OnAppUninstall", "获得守护进程pid-->" + gpid);
            editor.putString("pid", gpid);
            editor.commit();
        }
    }

    /**
     * 检查安装
     * 
     * @param context
     * @param packageName
     * @return
     * @author SHANHY
     * @date   2015年12月15日
     */
    private static boolean checkInstall(Context context, String packageName) {
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
            if (packageInfo == null) {
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取 UserSerial
     * 
     * @param ctx
     * @return
     * @author SHANHY
     * @date   2015年12月15日
     */
    private static String getUserSerial(Context ctx) {
        Object userManager = ctx.getSystemService("user");
        if (userManager == null) {
            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) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        } catch (InvocationTargetException e) {
        }
        return null;
    }

    /**
     * 检查子进程
     * 
     * @param context
     * @param mpid
     * @return
     * @author SHANHY
     * @date   2015年12月15日
     */
    private static boolean checkChildProcess(Context context, String mpid) {
        boolean resflag = false;
        int mypid = android.os.Process.myPid();
        Log.i("OnAppUninstall", "mypid-->" + mypid);
        BufferedReader in = null;
        long starttime = System.currentTimeMillis();
        List<Map<String, String>> listdata = new ArrayList<Map<String, String>>();
        try {
            Process p = Runtime.getRuntime().exec("ps");
            in = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            String[] temp;
            boolean flag = false;
//          int length = 0;
            while ((line = in.readLine()) != null) {
                // Log.i("OnAppUninstall", "ps-->" + line);
                if (!flag) {
                    flag = true;
                    continue;
                }
                line = line.replaceAll(" +", " ");
                temp = line.split(" ");
//              System.out.println(context.getPackageName());
//              System.out.println(temp[8]);
                if (temp[8].trim().equals(context.getPackageName().trim()) && (temp[2].trim().equals(mypid + "") || temp[2].trim().equals("1"))) {
                    Log.i("OnAppUninstall", "get it");
                    Map<String, String> map = new HashMap<String, String>();
                    map.put("pid", temp[1]);
                    map.put("pname", temp[8]);
                    listdata.add(map);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                in = null;
            }
        }
        long endtime = System.currentTimeMillis();
        long spendtime = endtime - starttime;
        for (int i = 0; i < listdata.size(); i++) {
            if (listdata.get(i).get("pid").equals(mpid)) {
                resflag = true;
            } else {
                Log.w("OnAppUninstall", "捕获到多余守护进程-->" + listdata.get(i).get("pid") + " " + listdata.get(i).get("pname"));
                try {
                    android.os.Process.killProcess(Integer.valueOf(listdata.get(i).get("pid").trim()));
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
            }
            Log.i("OnAppUninstall", listdata.get(i).get("pid") + " " + listdata.get(i).get("pname"));
        }
        Log.i("OnAppUninstall", "spendtime-->" + spendtime);
        return resflag;
    }
}
String url = "http://www.baidu.com";
UninstallObserver.openUrlWhenUninstall(this, url);

注:本文已经更新,并解决了之前“fork多个子进程”的问题。

目录
相关文章
|
2月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
228 4
|
2天前
|
Web App开发 编解码 vr&ar
使用Web浏览器访问UE应用的最佳实践
在3D/XR应用开发中,尤其是基于UE(虚幻引擎)开发的高精度场景,传统终端因硬件局限难以流畅运行高帧率、复杂效果的三维应用。实时云渲染技术,将渲染任务转移至云端服务器,降低终端硬件要求,确保用户获得流畅体验。具备弹性扩展、优化传输协议、跨平台支持和安全性等优势,适用于多种终端和场景,特别集成像素流送技术,帮助UE开发者实现低代码上云操作,简化部署流程,保留UE引擎的强大开发能力,确保画面精美且终端轻量化。
使用Web浏览器访问UE应用的最佳实践
|
1月前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
65 14
|
1月前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
1月前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
1月前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
38 0
|
2月前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
2月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
2月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
37 1