静默安装 收集

来源:互联网 发布:淘宝直播怎么加入 编辑:程序博客网 时间:2024/06/05 00:05
老早之前,写了一个android软件包静默安装的应用,放在工作空间很久了,今天整理项目的时候发现了,拿出来晒晒!
 
 软件实现的功能:当需要静默安装(即不弹出安装确认对话框,后台自动安装)时,启动安装服务,将待安装的软件包的路径放置到intent中,然后启动安装服务即可,在软件成功安装以后,会发送广播,你只需要接受对应的广播,即可获知软件是否安装成功!
 
 软件的关键代码:
Java代码    
1. class PackageInstallObserver extends IPackageInstallObserver.Stub {    
2.         public void packageInstalled(String packageName, int returnCode) {  
3.             Intent intent = new Intent(Constants.ACTION_INSTALLED);  
4.             intent.putExtra(Constants.EXTRA_INSTALL_RESULT, returnCode);  
5.             intent.putExtra(Constants.EXTRA_PACKAGE_NAME, packageName);  
6.             String apkName = map.get(packageName);  
7.             if( apkName != null )intent.putExtra(Constants.EXTRA_APK_NAME, apkName);  
8.             mContext.sendBroadcast(intent);  
9.         }    
10.     } 
  这个类通过名字大概可以看出来,就是一个观察者,当安装成功或者失败的时候,就会调用该类的packageInstalled方法,因此,我们可以将安装成功或者失败的响应代码写在这里,上面的类就是发送广播,告诉别人软件安装成功。
 
Java代码    
1. public void install(String apkPath,String apkName) {  
2.         File file = new File(apkPath);  
3.         if( !file.exists())  
4.             return ;  
5.         Uri mPackageURI = Uri.fromFile(file);  
6.         int installFlags = 0;  
7.         PackageManager pm = getPackageManager();  
8.         PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);  
9.         if(info != null){  
10.             try {  
11.                 PackageInfo pi = pm.getPackageInfo(info.packageName,PackageManager.GET_UNINSTALLED_PACKAGES);  
12.                 if( pi != null){  
13.                     installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;   
14.                 }  
15.             } catch (NameNotFoundException e) {  
16.             }  
17.             //把包名和apkName对应起来,后面需要使用  
18.             map.put(info.packageName, apkName);  
19.             IPackageInstallObserver observer = new PackageInstallObserver();  
20.             pm.installPackage(mPackageURI, observer, installFlags, info.packageName);  
21.         }  
22.     } 
  这个方法就是关键的软件包安装方法,首先检查给定的软件安装包是否存在,后面的代码就是判断系统当前是否已经安装了该软件,如果已经安装了,则设置安装参数为替换。
 pm.installPackage(mPackageURI, observer, installFlags, info.packageName);
  这行代码调用PackageManager的installPackage方法进行软件包安装,并注册一个观察者。
 
 另一种方式安装软件包,是通过发送intent请求,让系统来安装,所以我们无法控制安装提示界面。而该方式是自己直接调用系统的核心方法,跳过了安装确认界面,这样来实现静默安装。
 
 
 分析,因为这个api是系统影藏api,直接调用会报错,因此这个应用需要到源码环境下才能编译成功,关键源码环境的搭建可以参考博客:http://www.2cto.com/kf/201204/128606.html
 另外,由于是调用了系统隐藏api,所以这个应用也必须安装到系统app目录下才能够调用成功。因此,使用这种方式适合这样的需求:第一,适合于内置到rom中,第二,或者是获取了系统root权限,然后将该app拷贝到system/app目录,并设置好权限,现在android手机基本都获取了root权限,这种方式也不难实现。
 

 希望能给需要的人一点帮助~


============================================================================================================================


要求在安装APK应用时不显示权限信息和安装过程。

  1. Google的安全策略要求任何应用应该提示APK安装包的权限,对于一些内置特定厂商应用,可以跳过安装过程的信息加快安装,或者运营商强制安装。

  2. 这个功能的实现在src/package/app/PackageInstaller中,需要修改。添加静默安装的功能。又兼容正常安装。

实现的分析 

在窗口中点击一个APK时,触发单击事件,PackageInstaller接收系统服务PackageManagerService传来的intent信息,传来的Intent信息中有APK的一些参数。实现的关键是区分一般APK和特定APK

  1. 通过传给PackageManagerServiceintent中添加特别的参数,PackageInstaller接收后进行判断,进行特别的隐藏安装流程。这个实现只能通过程序调用的方式安装。 

  1. 安装过程的信息窗口在PackageInstallActivity.java中实现的。安装过程的信息窗口有4个:需要实现一个PakkageInstallActivityHide.JAVA的文件,去掉下面的dialog和窗口

    1. 安装权限确认窗口:installPermissionConfirm

    2. 安装进度条:installProgress

    3. 安装结果窗口:installResult

    4. 安装错误提示对话框

文件列表大概如下: 

InstallAppProgress.java 

PackageInstallerActivity.java

+PackageInstallerActivityHide.java

PackageUtil.java  

UninstallAppProgress.java

UninstallerActivity.java

+UninstallerActivityHide.java 

具体实现

1 在Androidmainfest.xml声明一个特定的intent:android.intent.action.VIEW.HIDE,由PackageInstallActivityHide.java来接受

注意这里的两点:

  1.  
  2. 把原先的 <application android:label="@string/app_name" android:theme="@android:style/Theme.Holo.DialogWhenLarge"> 
  3. 改成  <application android:label="@string/app_name">
  4. android:theme="@android:style/Theme.Holo.DialogWhenLarge"主题的显示放在每一个<activity 中,兼容正常按装的UI主题不变
  5.  
  6. 隐藏安装的PakkageInstallActivityHide <activity的主题只能是: 
  7. android:theme="@android:style/Theme.NoDisplay" 只能是这个,没有窗口 

内容如下:

  1. <application android:label="@string/app_name"> 
  2.  
  3. <activity android:name=".PackageInstallerActivity" android:theme="@android:style/Theme.Holo.DialogWhenLarge" android:configChanges="orientation|keyboardHidden">  
  4. <intent-filter>  
  5. <action android:name="android.intent.action.VIEW"/>  
  6. <category android:name="android.intent.category.DEFAULT"/>  
  7. <data android:scheme="content"/>  
  8. <data android:scheme="file"/>  
  9. <data android:mimeType="application/vnd.android.package-archive"/>  
  10. </intent-filter>  
  11. </activity>  
  12.  
  13. <activity android:name=".PackageInstallerHideActivity" android:theme="@android:style/Theme.NoDisplay" android:configChanges="orientation|keyboardHidden">  
  14. <intent-filter>  
  15. <action android:name="android.intent.action.VIEW.HIDE"/>  
  16. <category android:name="android.intent.category.DEFAULT"/>  
  17. <data android:scheme="content"/>  
  18. <data android:scheme="file"/>  
  19. <data android:mimeType="application/vnd.android.package-archive"/>  
  20. </intent-filter>  
  21. </activity>  
  22.  
  23. <activity android:name=".UninstallerActivityHide" android:theme="@android:style/Theme.NoDisplay" android:configChanges="orientation|keyboardHidden" android:excludeFromRecents="true">  
  24. <intent-filter>  
  25. <action android:name="android.intent.action.VIEW"/>  
  26. <action android:name="android.intent.action.DELETE.HIDE"/>  
  27. <category android:name="android.intent.category.DEFAULT"/>  
  28. <data android:scheme="package"/>  
  29. </intent-filter>  
  30. </activity>  
 

2 实现PakkageInstallActivityHide.java,UninstallerActivityHide.java。 只需把PakkageInstallActivity.java修改去掉dialog和对话框。

3 安装程序调用者发一个上面定义的intent即可。如下例子,静默安装/sdcard/hello.apk

卸载的方法类似。

  1. Intent install_hide_intent = new Intent("android.intent.action.VIEW.HIDE"); 
  2. install_hide_intent .setDataAndType(Uri.parse("file:///sdcard/hello.apk"), 
  3. "application/vnd.android.package-archive");  
  4. startActivityForResult(install_hide_intent, INSTALL_RUSULT); 

4 注意,这个方法需要PackageInstall这个apk必须与系统一起编译。这个apk在/system/app/目录下面;android.intent.action.VIEW.HIDE 这个静默安装的接口需要开放给第三方。

===================================================================================================================


(1)在网上搜寻该问题的解决方法,且查阅android开发文档,没有发现可以实现该功能的显示API调用,网络上很多人请教同样的问题,但都没有能够实现解答;说是android为了用户的安全,已屏蔽该实现该方法的功能,第三方法应用是无法实现静默安装的。
 (2)然后自己试图去看看android实现普通安装程序的源码文件,能否找到解决的办法,打算绕过普通安装时的提示框,直接调用通过确认后调用的函数进行安装;在查看android应用程序的普通安装过程后,发现应用程序安装过程的方法调用过程为:首先进入到com/android/packageinstaller/PackageInstallerActivity.java这个Activity中,在这个Activity中首先检查所欲安装的程序是否是正确的安装文件,以及当前系统中是否已安装了此应用程序,提示用户是否重复安装,另外还获取所欲安装的程序所讲用到的权限,然后将这些信息通过一个对话框提示给用户,当用户确定安装时,启动com.android.packageinstaller.InstallAppProgress.java这个Activity,在这个Activity中,调用

android.content.pm.PackageManager.installPackage(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName)进行安装应用程序,在InstallAppProgress中得到的PackageManager是通过PackageManager pm = getPackageManager()得到的,得到的对象是一个android.app.ContextImpl.ApplicationPackageManager对象,而

ApplicationPackageManager对象经过封装,

ApplicationPackageManager(ContextImpl context,
                IPackageManager pm) {
            mContext = context;
            mPM = pm;
        }
其installPackage方法为
  @Override
        public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
                String installerPackageName) {
            try {
                mPM.installPackage(packageURI, observer, flags, installerPackageName);
            } catch (RemoteException e) {
                // Should never happen!
            }
        }

可见调用的installPackage方法为 IPackageManager.installPackage(packageURI, observer, flags, installerPackageName);
在ContextImpl中,由IPackageManager pm = ActivityThread.getPackageManager()获得IPackageManager实例对象;在ActivityThread.getPackageManager()方法中,
static IPackageManager sPackageManager;
 public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
        IBinder b = ServiceManager.getService("package");
        sPackageManager = IPackageManager.Stub.asInterface(b);
        return sPackageManager;
    }

最终得到的installPackage确实是IPackageManager.installPackage方法;

因为class PackageManagerService extends IPackageManager.Stub所以IPackageManager.installPackage调用的是:PackageManagerService.java (frameworks/base/services/java/com/android/server)文件中的
 /* Called when a downloaded package installation has been confirmed by the user */
    public void installPackage(
            final Uri packageURI, final IPackageInstallObserver observer, final int flags) {
        installPackage(packageURI, observer, flags, null);
    }
(这里不明白为何IPackageManager.installPackage方法调用的是PackageManagerService.java,只是在网上的一篇文章中它给出了上面的原因,因为class PackageManagerService extends IPackageManager.Stub,我不明白,但也找不到其他的函数,通过PackageManagerService.java的源码,可以看出它确实是进行应用程序安装的,所以应该可以确定最终调用的方法就是

PackageManagerService.installPackage(final Uri packageURI, final IPackageInstallObserver observer, final int flags))

于是考虑如何得到PackageManagerService.installPackage(),考虑通过反射的方法得到installPackage(),但其中难以得到的是其参数中的IPackageInstallObserver类型,IPackageInstallObserver是由aidl文件定义的,通过aidl文件的特性,将IPackageInstallObserver.aidl文件拷到本地程序中,可以得到类IPackageInstallObserver.calss,通过它反射出installPackage()方法,但在invoke该方法时,却无法得到IPackageInstallObserver的实例对象,IPackageInstallObserver的实例对象必须通过

IPackageInstallObserver.Stub.asInterface(IBinder binder)方式得到,无法得到与其绑定的IBinder对象,因而无法执行反射出来的方法;另外PackageManagerService.installPackage()似乎是不能被第三方应用程序执行的,有权限的限制,这从下面的实例中似乎可以得到证实。

 (3)在程序中执行Runtime.getRuntime().exec("pm install -r " + new File(Environment.getExternalStorageDirectory(),
"download/Shuffle-1.6.3.apk")); 进行安装,这个命令的执行在 com.android.commands.pm.Pm中,直接调用IPackageManager.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,installerPackageName)方法,在此方法中,
IPackageManager mPm;
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
且class PackageManagerService extends IPackageManager.Stub
所以IPackageManager.installPackage调用的是:PackageManagerService.java (frameworks/base/services/java/com/android/server)文件中的
 /* Called when a downloaded package installation has been confirmed by the user */
    public void installPackage(
            final Uri packageURI, final IPackageInstallObserver observer, final int flags) {
        installPackage(packageURI, observer, flags, null);
    }

在此方法执行中会出现 Not granting permission android.permission.DELETE_PACKAGES错误,这应该是该权限不能授给第三方应用,因而在程序中不能执行,与android中普通安装应用程序最终调用的方法是相同的,但是却对第三方应用是没有权限执行的。。

(4) 另外解决思路:

 1> 使用android:sharedUserId="android.uid.system"属性来使应用程序获得系统权限,看看是否能够执行行Runtime.getRuntime().exec("pm install -r ... ")方法。
 2> 阅读android实现应用程序安装更底层的代码,看看能否可以调用的底层方法进行安装或者自己实现一个安装程序的代码,但这可能性不大,因为这涉及到android更底层的调用,

肯定会有一定的权限限制。
 3> 在网上看到一个文件管理程序,据说是可以实现批量寂寞安装应用程序,但说明运行时需要用户确定得到手机的root权限,所以没有太大意义。
 4> 定制自己的android系统,可以解决。

=======================================================================================================================================


通常情况下,android是没有提供静默方式的上层接口,我们需要在android源代码下来调用这个隐藏的接口来完成静默安装。
最重要的就是参考android系统目录下的packages/apps/PackageInstaller,
当中有两个文件 PackageInstallerActivity.java,InstallAppProgress.java ,前者就是我们通常看到的带有提示对话框的安装应用程序,后者是点确定安装后调用的intent。
现提供一个静默安装的关键类,该类在android2.2下成功编译, 其中通过循环调用接口instatllBatch则可实现批量安装
当然最后的应用程序别忘记添加权限

1<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
2    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
3    <uses-permission android:name="android.permission.DELETE_PACKAGES" />
4    <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
5    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
6    <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />

查看源码
打印?
001package com.android.util;
002 
003import java.io.File; 
004 
005import java.io.FileNotFoundException; 
006 
007import java.io.FileOutputStream; 
008 
009import java.io.IOException; 
010 
011import android.content.Context; 
012 
013import android.content.Intent; 
014 
015import android.content.pm.PackageInfo; 
016 
017import android.content.pm.PackageManager; 
018 
019import android.content.pm.PackageManager.NameNotFoundException; 
020 
021import android.content.pm.ApplicationInfo;
022 
023import android.content.pm.PackageParser;
024 
025import android.net.Uri; 
026 
027import android.util.Log; 
028 
029import android.util.DisplayMetrics;
030 
031import android.content.pm.IPackageInstallObserver; 
032 
033import android.content.pm.IPackageDeleteObserver; 
034 
035import android.os.FileUtils; 
036 
037import android.os.Handler;
038 
039import android.os.Message;
040 
041  
042 
043public class PackageInstaller { 
044 
045  
046 
047private File mTmpFile; 
048 
049private final int INSTALL_COMPLETE = 1;
050 
051final static int SUCCEEDED = 1;
052 
053final static int FAILED = 0;
054 
055private final static String TAG = "PackInstaller"
056 
057private Context mContext; 
058 
059private ApplicationInfo mAppInfo;
060 
061public PackageInstaller(Context context) { 
062 
063mContext = context; 
064 
065
066 
067public void install(String path,String packageName){ 
068 
069Intent intent = new Intent(Intent.ACTION_VIEW); 
070 
071intent.setDataAndType(Uri.fromFile(new File(path)), 
072 
073"application/vnd.android.package-archive"); 
074 
075mContext.startActivity(intent); 
076 
077
078 
079  
080 
081public void instatllBatch(String path) { 
082 
083Log.i(TAG, "path=" + path); 
084 
085int installFlags = 0
086 
087Uri mPackageURI  = Uri.fromFile(new File(path));
088 
089PackageParser.Package mPkgInfo = getPackageInfo(mPackageURI);
090 
091mAppInfo =  mPkgInfo.applicationInfo;
092 
093String packageName = mAppInfo.packageName;
094 
095Log.i(TAG, "====install packageName ="+packageName);
096 
097PackageManager pm = mContext.getPackageManager(); 
098 
099try 
100 
101PackageInfo pi = pm.getPackageInfo(packageName, 
102 
103PackageManager.GET_UNINSTALLED_PACKAGES); 
104 
105if (pi != null) { 
106 
107installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; 
108 
109
110 
111catch (NameNotFoundException e) { 
112 
113
114 
115if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { 
116 
117Log.w(TAG, "Replacing package:" + packageName); 
118 
119
120 
121  
122 
123PackageInstallObserver observer = new PackageInstallObserver(); 
124 
125pm.installPackage(mPackageURI, observer, installFlags, 
126 
127packageName); 
128 
129
130 
131private class PackageInstallObserver extends IPackageInstallObserver.Stub { 
132 
133public void packageInstalled(String packageName, int returnCode) { 
134 
135// Message msg = mHandler.obtainMessage(INSTALL_COMPLETE); 
136 
137// msg.arg1 = returnCode; 
138 
139// mHandler.sendMessage(msg); 
140 
141Log.i(TAG, "====INSTALL_COMPLETE"); 
142 
143
144 
145
146 
147private class PackageDeleteObserver extends IPackageDeleteObserver.Stub { 
148 
149public void packageDeleted(boolean succeeded) { 
150 
151//            Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE); 
152 
153//            msg.arg1 = succeeded?SUCCEEDED:FAILED; 
154 
155//            mHandler.sendMessage(msg); 
156 
157Log.i(TAG, "====UNINSTALL_COMPLETE"); 
158 
159
160 
161
162 
163    public void uninstall(String packageName){ 
164 
165Uri packageURI = Uri.parse("package:" + packageName); 
166 
167Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, 
168 
169packageURI); 
170 
171mContext.startActivity(uninstallIntent); 
172 
173
174 
175  
176 
177public void uninstallBatch(String packageName) { 
178 
179PackageDeleteObserver observer = new PackageDeleteObserver(); 
180 
181mContext.getPackageManager().deletePackage(packageName, observer, 0); 
182 
183  
184 
185
186 
187/* 
188 
189*  Utility method to get package information for a given packageURI          
190 
191*/
192 
193public  PackageParser.Package getPackageInfo(Uri packageURI) {
194 
195final String archiveFilePath = packageURI.getPath();
196 
197PackageParser packageParser = new PackageParser(archiveFilePath);
198 
199File sourceFile = new File(archiveFilePath);
200 
201DisplayMetrics metrics = new DisplayMetrics();
202 
203metrics.setToDefaults();
204 
205PackageParser.Package pkg =  packageParser.parsePackage(sourceFile,
206 
207archiveFilePath, metrics, 0);
208 
209// Nuke the parser reference.
210 
211packageParser = null;
212 
213return pkg;
214 
215}
216 
217/*
218 
219* Utility method to get application information for a given packageURI
220 
221*/
222 
223public   ApplicationInfo getApplicationInfo(Uri packageURI) {
224 
225final String archiveFilePath = packageURI.getPath();
226 
227PackageParser packageParser = new PackageParser(archiveFilePath);
228 
229File sourceFile = new File(archiveFilePath);
230 
231DisplayMetrics metrics = new DisplayMetrics();
232 
233metrics.setToDefaults();
234 
235PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
236 
237if (pkg == null) {
238 
239return null;
240 
241}
242 
243return pkg.applicationInfo;
244 
245}
246 
247private Handler mHandler = new Handler() {
248 
249public void handleMessage(Message msg) {
250 
251switch (msg.what) {
252 
253case INSTALL_COMPLETE:
254 
255if(msg.arg1 == SUCCEEDED) {
256 
257  
258 
259else {}
260 
261break;
262 
263default:
264 
265break;
266 
267}
268 
269}
270 
271};
272 
273}

原创粉丝点击