手机中的全部浏览器的app,不会被用户察觉,可是最后百度浏览器还是用反侦察技术找到这个邪恶的应用然后将其告上法庭了。那么我们就来看看怎么能够实现应用的静态安装和卸载呢?就是不让用户知道,以下就来一步一步的介绍一下实现步骤:
一、訪问隐藏的API方式进行静态的默认安装和卸载
1.系统安装程序
android自带了一个安装程序---/system/app/PackageInstaller.apk.大多数情况下。我们手机上安装应用都是通过这个apk来安装
的。代码使用也非常easy:
/* 安装apk */ public static void installApk(Context context, String fileName) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(Uri.parse("file://" + fileName),"application/vnd.android.package-archive"); context.startActivity(intent); } /* 卸载apk */ public static void uninstallApk(Context context, String packageName) { Uri uri = Uri.parse("package:" + packageName); Intent intent = new Intent(Intent.ACTION_DELETE, uri); context.startActivity(intent); }
通过发一个Intent,把应用所在的路径封装整uri.之后默认启动了PackageInstaller.apk来安装程序了。
可是此种情况下。仅仅是个demo而已,非常难达到开发人员的需求。如:
1).界面不好
2).被用户知晓
3).什么时候安装完了。卸载完了呢?
当然这里关于第三点的话,为了达到自己的需求。相信非常多人都会接着来监听系统安装卸载的广播,继续接下来的代码逻辑。
监听系统发出的安装广播
在安装和卸载完后。android系统会发一个广播
android.intent.action.PACKAGE_ADDED(安装)
android.intent.action.PACKAGE_REMOVED(卸载)
咱们就监听这广播。来做响应的逻辑处理。实现代码:
public class MonitorSysReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent){ //接收安装广播 if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) { //TODO } //接收卸载广播 if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) { //TODO } } }
<receiver android:name=".MonitorSysReceiver"> <intent-filter> <action android:name="android.intent.action.PACKAGE_ADDED" /> <action android:name="android.intent.action.PACKAGE_REMOVED" /> </intent-filter> </receiver>
到此,确实安装卸载的总体流程都知道了。可是这个效果肯定是无法达到项目的需求。
一般这样的应用商店类(豌豆荚)的项目。肯定是会要自己定义提示框效果的安装卸载功能,而不是调用系统的安装程序。
那咱就要想法子实现静默安装、卸载咯。
以下这样的调用系统隐藏api接口来实现静默安装卸载,是比較大众靠谱的,实现自己定义的提示界面
(关于这个调用系统隐藏的api接口,我们在之前讲到怎样获取手机电量的一篇文章中介绍过了
http://blog.csdn.net/jiangwei0910410003/article/details/25994337)
2.系统隐藏的api
隐藏api,顾名思义。一般情况下肯定是调用不到的。翻翻源代码frameworksasecorejavaandroidcontentpm文件夹下
PackageManager.java,应该发如今凝视行里有加上@hide声明。
调用的安装下载接口例如以下:
安装接口:
public abstract void installPackage(Uri packageURI,IPackageInstallObserver observer, int flags,String installerPackageName);
卸载接口:
public abstract void deletePackage(String packageName,IPackageDeleteObserver observer, int flags);
而且都是抽象方法,须要咱们实现。
看參数里IPackageInstallObserver observer一个aidl回调通知接口,当前文件夹中找到这接口:
package android.content.pm; /** * API for installation callbacks from the Package Manager. * @hide */ oneway interface IPackageInstallObserver { void packageInstalled(in String packageName, int returnCode); }
好吧,这里有现成的干货。咱拿过来直接用呗(当然假设没有源代码的那就算了,那能实现的仅仅是demo)。详细步骤:
从源代码中拷贝要使用的aidl回调接口:IPackageInstallObserver.aidl、IPackageDeleteObserver.aidl当然全然能够拷贝整个pm目
录。这样就不会报错了。
作者项目里面用到了pm,所以把PackageManager.java以及涉及到的一些文件也拷贝过来了,不然eclipse报找不到PackageManager
对象。结构例如以下:
注:此处的包名android.content.pm一定要和源代码文件夹结构一致,不然源代码里编译会提示找不到aidl接口。一切朝源代码编译看齐
此处有2种方式实现:
1.直接仅仅取IPackageDeleteObserver.aidl和IPackagerInstallObserver.aidl、IPackageMoveObserver.aidl等要使用的接口,然后通过
bindService来和系统连接服务,然后直接调用接口就可以(这样的没有方式作者没试过,只是原理上来说应该是可行的。除非系统没有
这个Service实现这个接口。有需求的能够深究下)
2.作者此处的方法是直接拷贝了源代码PackageManager.java等文件过来,只是靠过来之后eclipse会提示一些接口错误,但这里作者把
上面那几个.java文件都放空了,由于用不到,仅仅是为了编译过才拷贝了那么多文件。最简单的就是直接拷贝4个文件就可以:
PackageManager.java
IPackageDeleteObserver.aidl
IPackagerInstallObserver.aidl
IPackageMoveObserver.aidl
然后把PackageManager.java中报的异常的接口都凝视掉就可以
实现回调接口,代码例如以下:
安装的回调接口:
/*静默安装回调*/ class MyPakcageInstallObserver extends IPackageInstallObserver.Stub{ @Override public void packageInstalled(String packageName, int returnCode) { if (returnCode == 1) { Log.e("DEMO","成功安装"); new ToastThread(InstallActivity.this,"成功安装").start(); }else{ Log.e("DEMO","安装失败,返回码是:"+returnCode); new ToastThread(InstallActivity.this,"安装失败,返回码是:"+returnCode).start(); } } }
安装的方法:
String fileName = Environment.getExternalStorageDirectory() + File.separator + "baidu"+File.separator +"UC.apk"; Uri uri = Uri.fromFile(new File(fileName)); int installFlags = 0; PackageManager pm = getPackageManager(); try { PackageInfo pi = pm.getPackageInfo("com.UCMobile",PackageManager.GET_UNINSTALLED_PACKAGES); if(pi != null) { installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; } } catch (NameNotFoundException e) { } MyPakcageInstallObserver observer = new MyPakcageInstallObserver(); pm.installPackage(uri, observer, installFlags, "com.UCMobile");
从代码中能够看到我们想安装的是从SD卡中的baidu文件夹中的UC.apk
(所以在測试程序的时候须要将UC.apk复制到SD卡中的baidu文件夹中,或者你能够自己定义一个文件的存放文件夹)
卸载原理是一样的
卸载的回调接口:
/* 静默卸载回调 */ class MyPackageDeleteObserver extends IPackageDeleteObserver.Stub { @Override public void packageDeleted(String packageName, int returnCode) { if (returnCode == 1) { Log.e("DEMO","卸载成功..."); new ToastThread(InstallActivity.this,"卸载成功...").start(); }else{ Log.e("DEMO","卸载失败...返回码:"+returnCode); new ToastThread(InstallActivity.this,"卸载失败...返回码:"+returnCode).start(); } } }
卸载的功能:
PackageManager pm = InstallActivity.this.getPackageManager(); IPackageDeleteObserver observer = new MyPackageDeleteObserver(); pm.deletePackage("com.UCMobile", observer, 0);
这里我们一定要传一个包名。
自此。静默安装卸载代码实现。
最后在AndroidManifast.xml中要注冊权限和加入为系统用户组
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.autoinstallpackage.demo" android:versionCode="1" android:versionName="1.0.19" android:sharedUserId="android.uid.system"> <uses-sdk android:minSdkVersion="4"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.DELETE_PACKAGES" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> ... </manifest>
这里要注意的地方有两个:第一个必须要加入:
android:sharedUserId="android.uid.system"
这个是将其应用注冊成系统用户组中,假设不加这行代码的话,会出现报错。可是加了这行代码之后,假设还报错的话,重新clean一下项目就好了,这个是Eclipse的一个bug吧
还要加入安装和卸载的权限:
<uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.DELETE_PACKAGES" />
这时候上面的工作都做完之后,那么我们就来用Eclipse运行一下吧:
好吧。貌似不是那么的顺利,出现一个安装错误,事实上这个信息去百度一下之后会找到非常多的相关资料,由于这个问题有非常多人遇
到过。所以解决办法也是非常多的,就是须要将我们的应用签名成系统级的就可以。那么我们该怎么办呢?
网上普遍的两种方法:
第一种是:得到platform.pem,platform.x509.pem,signapk.jar这三个文件进行签名:签名的命令非常easy:
java -jar signapk.jar platform.x509.pem platform.pem 须要签名的apk 签名之后的apk
下图是我签名的操作:
1.假设你有系统源代码。最简单的就是将eclipse里面的应用复制到系统里面。然后编译系统,会生
成out/target/product/generic/system/app/abc.apk,这个应用就是经过系统签名了。详细方法例如以下:
将应用文件夹复制到源代码文件夹:packages/experimental/project_name/里面。而且仅仅保留src、res、libs、androidmanifest.xml这三个文件,里面新建一个Android.mk文件。内容例如以下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := abc
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
然后进行编译就可以
下来然后进行签名。结果总是失败,于是,我就放弃了从网上去下载,那么还有什么方法能够得到呢?好吧,还得去下载Android源
码。编译之后能够在指定的文件夹中找到这三个文件
可是还没有完,无论是第一种方法还是另外一种方法,我在试验的过程中还是失败,原因是什么呢?由于我们假设用这三个文件进行
签名或者把project放到源代码中进行编译的话,都必须要注意一点:就是签名的文件的系统版本号必须和安装到设备的系统的版本号一样,
说白一点:如今假如签名的那三个文件是从下载的编译之后的源代码中得到的,那么这三个文件的版本号就是这个Android源代码的版本号:
比方是4.4版本号的,那么我们用这三个文件进行签名之后的apk包仅仅能安装到系统版本号为4.4的机子上,同理假设我们将project直接放到
Android源代码中进行编译的话。假如Android源代码的系统版本号是4.4,那么编译之后的apk也仅仅能安装到系统版本号为4.4的机子上。
所以我们从网上下载到的签名的三个文件进行签名之后总是失败。所以这样的方式总是不靠谱的。由于我们从网上下载的签名文件肯
定不知道他的版本号号,可是我们能够将签名之后的包安装到每一个版本号的系统中进行尝试,所以最靠谱的方式还是我们下载源代码然后
得到这三个签名的文件就可以,由于我们自己下载的源代码肯定知道版本号的。
这三个文件相应的文件夹是:
signapk.jar: 源代码文件夹/out/host/linux-x86/framework/signapk.jar
platform.pk8和platform.x509.pem: 源代码文件夹/build/target/product/security/platform.pk8&platform.x509.pem
当我们使用第一种方法的时候。我们须要用Eclipse中导出一个未签名的包,这个非常easy了。这里就不介绍了~~
运行的结果:
由于手头上没有4.4系统的机子,又不想刷机。所以就用模拟器了。
在这个过程中安装和卸载可能要一段时间,所以要等待一会。不要着急~~
那么我们就介绍了使用隐藏的api来进行默认的安装和卸载app,
Demoproject下载地址:
http://download.csdn.net/detail/jiangwei0910410003/7584423
导入project之后,须要做的几步:
在SD卡中拷贝一个UC.apk到baidu文件夹(须要新建的)
导出未进行签名的包
下载签名的工具:
http://download.csdn.net/detail/jiangwei0910410003/7584441
依照上面的签名步骤进行操作
然后找到一个系统是4.4版本号的測试机或者模拟器进行安装包(由于我的签名文件的版本号是4.4)
二、通过命令进行静态的默认安装和卸载
1、须要签名文件,不须要root权限
以下我们再来看一下怎样使用命令的方式来实现默认的安装和卸载app,我们知道能够使用命令:
adb install apk文件
adb uninstall 包名
进行安装和卸载操作。
我们在代码中能够使用Runtime类进行操作:
Runtime.getRuntime().exec("adb uninstall com.UCMobile");
或者ProcessBuilder:
new ProcessBuilder().command("adb","uninstall","com.UCMobile").start();
看到这两个类的差别在于,第一种是将命令一起写完,另外一种是将命令用空格分隔,然后将其当做參数进行传递的。事实上command
方法的參数是一个String可变參数类型的
可是我们运行上面的代码发现总是运行失败,结果才发现,这些命令运行的文件夹不是在shell层的,可是手机中运行的命令必须
要在shell层中的,所以会有问题的。可是我们能够使用pm命令。比方我们使用PC端的命令行进行安装文件:
>adb shell
>pm install apk文件
事实上上面的两行命令和
>adb install apk文件
的效果一样
可是假设想在手机上运行安装文件仅仅能运行命令:pm install apk文件
(能够把pm命令的作用想成是脱离shell层来运行命令的)
不多说了,以下就来看一下代码吧:
/* * m命令能够通过adb在shell中运行。相同,我们能够通过代码来运行 */ public static String execCommand(String ...command) { Process process=null; InputStream errIs=null; InputStream inIs=null; String result=""; try { process=new ProcessBuilder().command(command).start(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int read = -1; errIs=process.getErrorStream(); while((read=errIs.read())!=-1){ baos.write(read); } inIs=process.getInputStream(); while((read=inIs.read())!=-1){ baos.write(read); } result=new String(baos.toByteArray()); if(inIs!=null) inIs.close(); if(errIs!=null) errIs.close(); process.destroy(); } catch (IOException e) { result = e.getMessage(); } return result; }
调用这种方法:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final String path = Environment.getExternalStorageDirectory() + File.separator + "baidu"+File.separator + "360MobileSafe.apk"; Button btn1 = (Button) findViewById(R.id.btn1); btn1.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { //安装apk,filePath为apk文件路径,如/mnt/sdcard/ApiDemos.apk String result = execCommand("pm","install","-f",path); Toast.makeText(MainActivity.this, "安装结果:"+result, Toast.LENGTH_LONG).show(); }}); Button btn2 = (Button) findViewById(R.id.btn2); btn2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { //卸载apk。packageName为包名,如com.example.android.apis String result = execCommand("pm","uninstall", "com.qihoo360.mobilesafe"); Toast.makeText(MainActivity.this, "卸载结果:"+result, Toast.LENGTH_LONG).show(); }}); }
在AndroidManifest.xml中的配置:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.xieyuan.mhfilemanager" android:versionCode="1" android:versionName="1.0" android:installLocation="internalOnly" android:sharedUserId="android.uid.system" >也有:
android:sharedUserId="android.uid.system"
这个在前面已经说过了。所以我们如今做的还是将其进行系统签名,详细方法就不多说了,同上
以下是运行的结果图:
Demoproject下载地址:http://download.csdn.net/detail/jiangwei0910410003/7584429
下载之后的project导入之后须要进行的几步操作和前面的是一模一样的,这里就不做解释了~~
可是我们从上面的方法看到有一个弊端,还是须要系统签名,这个对于一般app来说是个限制。如今机型非常多。系统的签名文件也都不一样,所以非常难做到这样的方式能够适配到全部的机型。
那么还有另外的一种方法:
2、不须要签名文件,可是须要root权限
这样的方式非常easy和上面的没有太大的差别,就是将上面的命令放到su以下去运行就能够了。
从这里我们能够看到,如今非常多须要系统签名的或者是命令不能运行的,都会想到su环境中去运行以下。
这里不多解释了,直接上代码:
public static boolean RootCommand(String command){ Process process = null; DataOutputStream os = null; try{ process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); os.writeBytes(command + " "); os.writeBytes("exit "); os.flush(); process.waitFor(); } catch (Exception e){ Log.d("*** DEBUG ***", "ROOT REE" + e.getMessage()); return false; } finally{ try{ if (os != null){ os.close(); } process.destroy(); } catch (Exception e){ } } Log.d("*** DEBUG ***", "Root SUC "); return true; }然后我们调用这种方法来运行命令就可以:
new Thread(){ @Override public void run(){ RootCommand("pm uninstall com.tencent.mm"); } }.start();这里就能够运行了,这样的方式能够适配全部的机型,可是须要root权限,这个就比較麻烦了。可是如今非常多安全应用大部分都是採用这样的方式的
三、拷贝/删除apk文件实现安装和卸载
这个方式事实上非常easy。就是直接拷贝和删除文件就可以。我们知道Android中安装的apk都是放在/data/app/文件夹以下的,所以我们能够将我们须要安装的apk放到这个文件夹下就可以。
注:这样的方式是能够接收到安装和卸载的系统广播
首先我们在设备的SD卡中存放一个待安装的apk:360MobileSafe.apk
这里的文件夹能够随便选择的,我这里选择了SD卡的根文件夹
这里为了看到明显的效果,我们在写一个Demo程序。来接收安装的广播
后台监听广播的Service:
package com.example.installpackage; import android.app.Service; import android.content.Intent; import android.content.IntentFilter; import android.os.IBinder; public final class MyService extends Service { @Override public void onCreate() { super.onCreate(); IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); filter.setPriority(Integer.MAX_VALUE); registerReceiver(new InstallReceiver(), filter); } @Override public IBinder onBind(Intent paramIntent) { return null; } }
用动态方式进行注冊
广播广播接收器:
package com.example.installpackage; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public final class InstallReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { Log.i("TTT","action:"+intent.getAction()); } }
开启服务:
package com.example.installpackage; import android.os.Bundle; import android.app.Activity; import android.content.Intent; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent i = new Intent(this, MyService.class); startService(i); } }
还要记得在AndroidManifest.xml中进行注冊服务:
<?xml version="1.0" encoding="utf-8"?
> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.installpackage" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.installpackage.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".MyService"></service> </application> </manifest>
我们在PC端能够使用adb push XXX.apk /data/app/ 实现拷贝,可是在设备上这个命令不能运行。由于Android是底层是Linux系统,所以自带的有拷贝命令:
1、安装操作
1)、拷贝命令cp
=>adb shell
=>su
=>cd /sdcard/
=>cp 360MobileSafe.apk /data/app
如图效果:
在这个过程中,我们通过打印log信息能够看到。是有点耗时的。
当然除了这样的方式还有另外的一种方式:
2)、使用cat命令实现
我们知道cat命令有输出重定向的功能
=>adb shell
=>su
=>cd /sdcard/
=>cat 360MobileSafe.apk > /data/app/com.qihoo.360.apk
运行结果:
2、卸载操作
卸载也非常easy,我须要删除/data/app文件夹下指定的apk文件,可是我们知道,一个app安装好之后,不仅在这个文件夹中有数据,还有其它文件夹中也是有数据的:
/data/davlik-cache/ :存放释放的dex文件
/data/data/com.qihoo.360.mobilesafe/ :存放app的数据
/data/system/packages.list :这个文件是记录安装app的信息的
/data/system/packages.xml :这个文件是记录安装app的详细信息(权限。uid等)
命令:
=>adb shell
=>su
=>cd /data/app/
=>rm 360MobileSafe.apk
运行结果:
总结
上面就介绍了实现静态安装和卸载的功能,前面两种方法不须要root权限的,可是须要签名文件。这个对于不同机型,有不同的签名文件,(在另外一种方法中的两种实现不同的,不须要签名文件,可是须要root权限)这个是件非常头疼的事。第三种是不须要签名文件的,对于全部机型都适合,可是须要root权限的,如今非常多安全软件在实现静
默安装和卸载的时候也都是须要root权限才干进行操作的,而且每一个机型上都是能够的。所以他们应该採用的是另外一种方式中的另外一种实现或者是第三种方式。
注:前两种方式是不能够接受系统的安装和卸载广播的。第三种是能够接受系统的安装和卸载广播的。
待解决的问题
1. 上面我们能够看到我们使用的是4.4版本号进行签名的,仅仅能安装到系统是4.4版本号的机子上。这个有待考证,由于我们开发的应用不可能仅仅能装到指定的版本号中。必须是全部的系统都能进行安装(比方豌豆荚就能够),所以我就做了一个假设,是不是低版本号的签名文
件进行系统签名之后的apk能够安装到高版本号系统中,那么这样就好办了,我们能够下载Android2.2(由于如今市面上最低的版本号是
2.2了)源代码,得到他的签名文件,或者从网上搜索到这个版本号的签名文件(至今未找到,假设有找到的请分享,谢谢!!
)
2. 另一个问题就是上述的測试环境都是在手机或者模拟器都是root过了,有待考证的是没有root的手机会不会安装时出现故障~~
~~。事实上上面遇到的最大的问题就是在下载源代码过程(没有技术可言,就是考验我们的耐心,同一时候网络也是一个重要的原因,这里由
于某些原因。我下载的是4.4版本号的源代码,共13G,这里就不公开下载地址了。假设真心须要请留言。我能够共享一下~~),还有就是
编译过程,我花费了一周的时间。由于在这个过程中能够说是千难万险,什么问题都有,我又一次编译好几次~~
好吧,这里有现成的干货,咱拿过来直接用呗(当然假设没有源代码的那就算了,那能实现的仅仅是demo)。详细步骤:
从源代码中拷贝要使用的aidl回调接口:IPackageInstallObserver.aidl、IPackageDeleteObserver.aidl当然全然能够拷贝整个pm目
由于手头上没有4.4系统的机子。又不想刷机。所以就用模拟器了。在这个过程中安装和卸载可能要一段时间。所以要等待一会。不要着急~~
那么我们就介绍了使用隐藏的api来进行默认的安装和卸载app,
Demoproject下载地址:
http://download.csdn.net/detail/jiangwei0910410003/7584423
导入project之后,须要做的几步:
在SD卡中拷贝一个UC.apk到baidu文件夹(须要新建的)
导出未进行签名的包
下载签名的工具:
http://download.csdn.net/detail/jiangwei0910410003/7584441
依照上面的签名步骤进行操作
然后找到一个系统是4.4版本号的測试机或者模拟器进行安装包(由于我的签名文件的版本号是4.4)