应用程序如何检测到它将被卸载? [复制]

Posted

技术标签:

【中文标题】应用程序如何检测到它将被卸载? [复制]【英文标题】:How can an app detect that it's going to be uninstalled? [duplicate] 【发布时间】:2013-09-12 14:35:20 【问题描述】:

我们所知道的,通常(实际上任何)防病毒应用程序在卸载之前都会触发简单的对话框,例如:“你要卸载应用程序,你确定吗?” - “是/否”。

是的,我知道我可以使用意图过滤器拦截包删除意图,例如:

<activity
    android:name=".UninstallIntentActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <action android:name="android.intent.action.DELETE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="package"  />
    </intent-filter>
</activity>

但问题很简单,这会拦截 任何 删除请求,而且这会在我的应用程序和股票安装程序之间触发选择器对话框。因此,如果用户选择股票安装程序 - 我将无能为力。

我的目标不是阻止用户卸载我的应用,而只是回滚我的应用所做的更改。

从那些杀毒应用中了解到这种操作是可能的,所以请帮助我并解释它是如何可能的?

更新

由于有些人不相信这是真的 - 我会参考Avast Mobile Security:

Anti-Theft 通过伪装其组件来保护自己免于卸载 使用各种自我保护技术。

另一个示例:Android 版卡巴斯基安全软件 - here's special procedure for uninstalling it,需要输入密码。

无论如何,这意味着有办法拦截卸载过程,以防止卸载或完成一些最终工作。

【问题讨论】:

您的应用做了哪些更改需要回滚? 这些应用程序监控 logcat 并在检测到您尝试卸载应用程序时要求您输入 pin。他们这样问你,只需在正确的时间开始一项活动:这项活动自然会出现在你和安装的股票之间。 旁注:我刚刚安装了“防病毒”应用程序并在没有应用程序询问我任何内容的情况下将其卸载。 API 18. @SherifelKhatib - 自从监视 logcat 的功能被删除以来,已经有几个 android 版本了。 大多数防病毒和防盗应用程序都以设备管理员的身份自行安装。看到这个 - developer.android.com/guide/topics/admin/device-admin.html 【参考方案1】:

如果你修改内核可能 .在 data/data/your.app.package 中创建的所有文件都会在安装时自动删除。

另一种方法是让另一个应用程序检查该应用程序是否已安装。如果没有,它可以做清理工作。

更新

ACTION_PACKAGE_REMOVED Intent 将发送给除您自己之外的所有接收者。已确认HERE。

更新 2

只是另一个想法。

当我搜索这个时我发现,这可以通过监控你的应用程序的 logcat 来完成 here 是一个示例 logcat 监视器

好消息是,要监控同一应用程序的 logcat,我们不需要有根设备

当我们读取 logcat 中的每个条目时,我们可以搜索以下字符串

Received broadcast Intent  act=android.intent.action.PACKAGE_REMOVED dat=package:com.package.name flg=0x8000010 (has extras) 

收到此事件后,我们知道我们的应用现在将被卸载

没试过

Android Jellybean 不允许再次监控 logcat

【讨论】:

执行以下操作: 1. 在您的设备上安装 Avast Antivirus 应用程序 2. 尝试卸载它 3. 您将看到 Avast 将触发对话框询问“您确定吗?”附言它几乎发生在任何设备中(我的意思是无根的)。然后你说这是不可能的?! 是的,我已经看到了,我已经在回答中提到了它是如何完成的,请仔细阅读答案 对答案投赞成票,对问题投反对票……赏金不会改变现实。 @ChrisStratton 您应该阅读 cmets 线程。作者举了一个市场上的应用程序 (Avast) 的示例,该应用程序成功运行,因此现实可能比您想象的要复杂。 你这么说很有趣,考虑到你自己在之前的评论中指出,发帖人引用的应用程序并没有像发帖人声称的那样做......【参考方案2】:

为了使您的应用持久存在,您需要有一个已获得根目录的设备并能够将其安装到系统分区。一旦它在那里,您就可以卸载更新,因为它们与非系统应用程序一起保存,但从系统中卸载它并不是那么简单。

我知道其中一些还会在系统分区上保存一些数据,以防设备恢复出厂设置,但也有一些方法可以让包管理器留下您保存的数据,以防万一刚刚卸载。

另一种选择是将其注册为设备管理员。一旦你这样做,他们将无法卸载它,除非他们手动删除它的管理员状态。

&lt;item name="android.permission.ACCESS_SUPERUSER" /&gt;

这里看起来他们正在使用 root 以及其他方法。除了提供一些他们可能拥有的疯狂的精心服务之外,没有其他任何合法的方式可以做到这一点。

利用 root 几乎是像这样的 AV/安全应用程序的标准做法,没有它,它们对任何其他应用程序没有任何真正的权限,因此它们非常有限。我认为除非您也安装了 SuperUser 权限,否则不会显示它,所以很多人仍然不知道这是一个选项。

<perms>
<item name="android.permission.READ_EXTERNAL_STORAGE" />
<item name="android.permission.GET_TASKS" />
<item name="android.permission.PROCESS_OUTGOING_CALLS" />
<item name="android.permission.WRITE_EXTERNAL_STORAGE" />
<item name="android.permission.WRITE_CALL_LOG" />
<item name="com.avast.android.generic.CENTRAL_SERVICE_PERMISSION" />
<item name="android.permission.WRITE_SMS" />
<item name="android.permission.ACCESS_WIFI_STATE" />
<item name="android.permission.RECEIVE_SMS" />
<item name="android.permission.GET_ACCOUNTS" />
<item name="android.permission.READ_CONTACTS" />
<item name="android.permission.CALL_PHONE" />
<item name="android.permission.WRITE_CONTACTS" />
<item name="android.permission.READ_PHONE_STATE" />
<item name="android.permission.READ_SMS" />
<item name="android.permission.RECEIVE_BOOT_COMPLETED" />
<item name="android.permission.ACCESS_SUPERUSER" />
<item name="com.avast.android.mobilesecurity.permission.C2D_MESSAGE" />
<item name="android.permission.GET_PACKAGE_SIZE" />
<item name="android.permission.WAKE_LOCK" />
<item name="android.permission.ACCESS_NETWORK_STATE" />
<item name="android.permission.USE_CREDENTIALS" />
<item name="android.permission.SEND_SMS" />
<item name="android.permission.RECEIVE_MMS" />
<item name="com.google.android.c2dm.permission.RECEIVE" />
<item name="android.permission.KILL_BACKGROUND_PROCESSES" />
<item name="com.android.vending.BILLING" />
<item name="android.permission.WRITE_SETTINGS" />
<item name="android.permission.INTERNET" />
<item name="android.permission.VIBRATE" />
<item name="android.permission.READ_CALL_LOG" />
<item name="com.avast.android.generic.COMM_PERMISSION" />
<item name="com.dolphin.browser.permission.ACCESS_PROVIDER" />
<item name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
</perms>

【讨论】:

如 OP 和 cmets 中所述,Avast Mobile Security 无需 root 即可执行此操作。在无根的 4.2.2 Galaxy Nexus 上测试和验证。 有很多方法可以做,只是取决于你愿意付出多少努力,以及你希望它有多彻底。【参考方案3】:

好的。两天以来我一直在研究这个问题,终于找到了一种“野蛮的方法”来解决它,而无需 root 设备:)

首先,实现解决方案的要点如下:

1. 每当用户进入设置 -> 管理应用程序 -> 选择特定应用程序时 我们会收到一个广播 android.intent.action.QUERY_PACKAGE_RESTART,其中包含应用程序包的名称作为附加信息。

2. 之后,当我们点击 Uninstall 按钮(带有包安装程序)时,它会打开一个名为 - com.android.packageinstaller.UninstallerActivity 的活动

控制流程如下:

在应用程序设置下,用户单击卸载按钮 ---> 我们可以控制显示对话/开始另一个活动 / 等等 ---> 我们完成了预卸载任务 ---> 用户返回返回卸载确认屏幕 ---> 用户确认并卸载应用程序

使用方法:

我们将在我们的应用程序中实现一个 BroadcastReceiver 来监听动作“android.intent.action.QUERY_PACKAGE_RESTART”并在 onReceive() 方法中匹配我们的包名。如果收到广播以选择我们所需的应用程序包,那么我们将启动一个后台线程,该线程将使用 ActivityManager 继续监视前台运行的活动。

一旦我们发现前台活动是“com.android.packageinstaller.UninstallerActivity”,就会确认用户想要卸载我们的应用程序。此时,我们将执行卸载前要执行的所需任务(显示对话框,或启动另一个与卸载窗口重叠的活动等)。执行我们的任务后,我们将允许用户继续确认卸载过程。

实现/源代码:

在 manifest.xml 中

添加权限:

<uses-permission android:name="android.permission.GET_TASKS"/>

和广播接收器:

<receiver android:name=".UninstallIntentReceiver">
      <intent-filter android:priority="0">
            <action android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
            <data android:scheme="package" />
      </intent-filter>
 </receiver>

UninstallIntentReceiver.java(广播接收器类)

public class UninstallIntentReceiver extends BroadcastReceiver

    @Override
    public void onReceive(Context context, Intent intent) 
        // fetching package names from extras
        String[] packageNames = intent.getStringArrayExtra("android.intent.extra.PACKAGES"); 

        if(packageNames!=null)
            for(String packageName: packageNames)
                if(packageName!=null && packageName.equals("YOUR_APPLICATION_PACKAGE_NAME"))
                    // User has selected our application under the Manage Apps settings
                    // now initiating background thread to watch for activity
                    new ListenActivities(context).start();

                
            
        
    


ListenActivities 类 - 用于监控前台活动

class ListenActivities extends Thread
    boolean exit = false;
    ActivityManager am = null;
    Context context = null;

    public ListenActivities(Context con)
        context = con;
        am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    

    public void run()

        Looper.prepare();

        while(!exit)

             // get the info from the currently running task
             List< ActivityManager.RunningTaskInfo > taskInfo = am.getRunningTasks(MAX_PRIORITY); 

             String activityName = taskInfo.get(0).topActivity.getClassName();


             Log.d("topActivity", "CURRENT Activity ::"
                     + activityName);

             if (activityName.equals("com.android.packageinstaller.UninstallerActivity")) 
                // User has clicked on the Uninstall button under the Manage Apps settings

                 //do whatever pre-uninstallation task you want to perform here
                 // show dialogue or start another activity or database operations etc..etc..

                // context.startActivity(new Intent(context, MyPreUninstallationMsgActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                 exit = true;
                 Toast.makeText(context, "Done with preuninstallation tasks... Exiting Now", Toast.LENGTH_SHORT).show();
             else if(activityName.equals("com.android.settings.ManageApplications")) 
                // back button was pressed and the user has been taken back to Manage Applications window
                          // we should close the activity monitoring now
                exit=true;
            
        
        Looper.loop();
    

已知限制:

当用户点击管理应用设置下的卸载按钮时,我们将执行我们的预卸载任务,然后提示用户进入确认窗口,用户可以在此确认卸载或可以取消操作。

上述方法目前尚未涵盖用户在我们完成任务后点击取消按钮的情况。但这可以通过一些修正轻松解决。

例如:如果最终没有收到广播“android.intent.action.PACKAGE_REMOVED”,我们可以实现一个逻辑来恢复我们所做的更改。

我希望这种方法对您有所帮助 :) 因为这是我认为我们可以在不生根设备的情况下解决您的问题的唯一方法!

[更新 1]: 检查卸载任务是否被取消的建议方法:

有趣的是,我之前有完全不同且非常复杂的想法(涉及广播、ActivityManager 等),但在这里写它时,我脑海中突然出现了另一个相对非常简单的想法 :)

当用户单击“管理应用程序设置”下的“卸载”按钮并且您执行了预卸载任务后,您只需在您的应用程序中设置一些 SharedPreference,即您已执行了预卸载任务并准备好卸载。在此之后,您无需关心任何事情。

如果用户继续卸载 -> 很好,因为您已经执行了所需的任务。

如果用户最终点击 Cancel 按钮并离开 -> 不要打扰。直到用户再次运行您的应用程序。现在在应用程序主要活动的“onStart()”/“onResume()”中,您可以检查 SharedPreference 的值,如果它被设置为卸载,这意味着用户最终没有继续卸载。现在您可以恢复之前所做的更改(反转执行的预卸载任务)以确保您的应用程序完美运行!

【讨论】:

在将其发布到此处之前,我确保它运行良好 :) 虽然我无法在更高版本上测试它,因为我的 Jelly Bean 手机正在维修 :( 如果您需要任何进一步的帮助,请告诉我。 感谢您的赏金!这几乎是我当前总声誉的 1.5 倍,所以对我来说意义重大:) 关于您的 2 个问题——1)您可以为此目的创建一个活动并启动它,如我的代码中注释的行所示。在此活动中,您可以警告/重新询问用户以确保.. 因此,您可以将他转发到主屏幕(例如最小化窗口,这将隐藏卸载屏幕,或者可能有一种使用 ActivityManager 的方法其实你可以关闭原来的包卸载窗口。我没试过,但你可以探索一下。 &lt;uses-permission android:name="android.permission.GET_TASKS"/&gt;API 级别 21弃用 只是想对更新 1 中的好主意表示感谢! 这个过程还有效吗?有人在最新的 API 级别上尝试过吗?【参考方案4】:

在 Android 5.0 之前,有检测应用程序卸载是否使用本机代码的选项:

您需要在分叉进程中使用inotify 框架监控您的目录。 删除时您可以运行一些系统命令,例如。 am 命令启动 Intent

此类方案的PoC:https://github.com/pelotasplus/ActionAfterUninstall/blob/master/app/src/main/jni/hello-jni.c

【讨论】:

当我在 4.4 中运行整个项目时,没有显示任何日志。你能告诉我当用户点击卸载按钮时我如何看到日志。 并不是说这不再适用于 Android 5.0:github.com/pelotasplus/ActionAfterUninstall/issues/1

以上是关于应用程序如何检测到它将被卸载? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 WiX 从源代码重建完全相同的 msi 文件?

本地Run Page时报检测到意外的 URL 参数,它将被忽略。

如何在操作系统检测到它之前或之后检测到 Ctrl-Alt-Del?

在mac终端中粘贴了privoxy程序,怎样卸载

NSIS 如何在卸载时检测软件是不是运行并提示用户

Windows Mobile 设备是不是可以检测到它何时停靠?