返回

Android隐身应用开发:创建无图标无界面的App

Android

Android 应用隐身术:创建无图标、无 Activity 的应用

朋友间的一次闲聊,让我发现了一个有趣的话题:在 Android 系统上,竟然可以安装没有图标、也没有界面的应用! 它只会在“管理应用程序”列表中默默存在。这对于开发者来说,意味着什么? 我们该怎么实现呢?

问题根源:Manifest 文件的玄机

Android 应用的“可见性”,完全由 AndroidManifest.xml 文件控制。具体来说,是看有没有配置 intent-filter, 包含特定的 actioncategory。通常情况下, 为了让应用出现在启动器(Launcher)里, 我们会这样配置:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
  • android.intent.action.MAIN:表示这是程序的主入口。
  • android.intent.category.LAUNCHER:表示这个 Activity 应该出现在启动器中,也就是会生成一个应用图标。

而要实现“隐身”,关键就在于:不要配置上述的 intent-filter! 一个没有任何 Activity,或者即便有 Activity 也不声明为 MAINLAUNCHER 的应用,自然就不会出现在启动器里。

实现方案:打造“隐形”应用

下面,分几种情况讨论如何创建这类应用。

方案一:纯后台服务,无任何 UI

如果你的应用完全不需要和用户交互,只需要默默在后台执行任务(例如,数据采集、定时任务等),那么,完全可以不创建任何 Activity。

  1. 创建项目: 使用 Android Studio 创建一个新项目,选择 "No Activity" 模板。

  2. 编写 Service: 创建一个继承自 Service 的类,并在 onStartCommand() 方法中实现你的后台逻辑。

    public class MyBackgroundService extends Service {
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // 在这里执行你的后台任务,例如:
            Log.d("MyBackgroundService", "Service is running...");
            // ... 你的代码 ...
    
            // 返回 START_STICKY,确保 Service 被系统杀死后能够自动重启
            return START_STICKY;
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
    
  3. 注册 Service:AndroidManifest.xml 文件中注册你的 Service。

    <manifest ...>
        <application ...>
            <service android:name=".MyBackgroundService" />
        </application>
    </manifest>
    

    注意:此处没有 配置任何 <activity> 标签!

  4. 安装运行 这样创建的应用,安装后不会有任何图标, 应用的任务交给了service来处理.

方案二:有 Activity,但隐藏

如果你确实需要 Activity(例如,接收某些隐式 Intent,或者提供 ContentProvider),但又不想让它显示在启动器中,可以这样操作:

  1. 创建 Activity: 正常创建你的 Activity 类。

  2. 修改 Manifest:AndroidManifest.xml 中,不要 给你的 Activity 添加 android.intent.action.MAINandroid.intent.category.LAUNCHERintent-filter。可以根据需要添加其他类型的 intent-filter

    <activity android:name=".MyHiddenActivity">
        <!-- 例如,接收一个自定义的 Intent -->
        <intent-filter>
            <action android:name="com.example.myapp.MY_CUSTOM_ACTION" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    
  3. 通过 adb shell am start启动应用:

adb shell am start -n com.yourpackage/.YourActivityName

举个例子:如果你在 AndroidManifest.xml中注册如下

        <activity android:name=".HiddenActivity">
            <intent-filter>
                 <action android:name="com.example.START_HIDDEN" />
                 <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
       </activity>

你需要用:

adb shell am start -a com.example.START_HIDDEN

启动你的应用.
这样,虽然应用有 Activity,但也不会在启动器中显示图标。

方案三: 进阶控制-动态显示/隐藏图标

还有更高级的玩法:在应用安装后,动态控制图标的显示和隐藏。这需要用到 PackageManagersetComponentEnabledSetting() 方法。

  1. 声明两个 Activity:

AndroidManifest.xml 声明两个, 一个是正常入口的activity,用来在启动器上显示,另外一个则相反.

<activity
    android:name=".MainActivity"
    android:enabled="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity-alias
   android:name=".MainActivityAlias"
   android:targetActivity=".MainActivity"
   android:enabled="false">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity-alias>

MainActivity的 enabled 为 true, 而 MainActivityAlias enabled为 false.

  1. 使用代码控制: 在代码中使用 PackageManager 切换这两个组件的启用状态。

    PackageManager pm = getPackageManager();
    ComponentName mainActivity = new ComponentName(this, MainActivity.class);
     ComponentName aliasActivity = new ComponentName(this, "com.yourpackage.MainActivityAlias"); //完整的类名
    
    // 隐藏图标
    pm.setComponentEnabledSetting(mainActivity,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP);
    
    pm.setComponentEnabledSetting(aliasActivity,
          PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
          PackageManager.DONT_KILL_APP);
    
    // 显示图标 (与上面相反的操作)
    // pm.setComponentEnabledSetting(mainActivity,
    //        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
    //        PackageManager.DONT_KILL_APP);
    

    这段代码先禁用 MainActivity(从而隐藏图标),然后启用 MainActivityAlias。 反过来,就能再次显示图标.

  2. 注意 通过 setComponentEnabledSetting 进行切换组件的可用状态可能会有一定的延时才会显示.

方案四:ContentProvider应用

如果应用只用于数据分享,不需要任何界面的时候,可以只注册ContentProvider.

  1. 创建 ContentProvider: 正常创建你的 ContentProvider 类。

  2. 修改 Manifest:AndroidManifest.xml 中注册你的provider。

        <provider
            android:name=".MyContentProvider"
            android:authorities="com.example.myapp.provider"
            android:exported="true" />

注意,exported 设为 true 使其可以被其它应用访问。这样别的应用可以用ContentResolver通过Uri来访问该应用提供的数据.

安全建议

“隐形”应用虽然有其特殊用途,但也有潜在的安全风险:

  • 恶意软件: 恶意软件可能利用这种方式隐藏自身,偷偷在后台执行恶意操作。普通用户很难发现它们。
  • 权限滥用: 即便没有界面,这类应用仍然可以申请各种权限(例如,访问联系人、位置信息、发送短信等),并在后台悄悄使用。

因此, 作为普通用户:

  • 谨慎安装来源不明的应用。
  • 定期检查“管理应用程序”列表, 看有没有可疑应用。
  • 留意权限申请, 即使是没有图标的应用,也要仔细看它申请了哪些权限。

而作为开发者, 应在正当合法的情景下使用这些技术,并且在开发过程中注意保护用户的隐私和数据安全。切记,技术本身没有好坏,关键在于如何使用!