android 实现静默安装、卸载(图)

android中应用的安装卸载,大家(用android设备的)肯定不陌生。这里就来浅谈android应用的安装、卸载的实现方式。

1.系统安装程序

android自带了一个安装程序---/system/app/PackageInstaller.apk.大多数情况下,我们手机上安装应用都是通过这个apk来安装的。代码使用也非常简单:


/* 安装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而已,很难达到开发者的需求。如:
 

  • 界面不好

  • 什么时候安装完了,卸载完了呢?

为了达到自己的需求,相信很多人都会接着来监听系统安装卸载的广播,继续接下来的代码逻辑。


2.监听系统发出的安装广播

在安装和卸载完后,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
        }
    }
}

AndroidMenifast.xml里配置:



  <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>


到此,确实安装卸载的整体流程都知道了,但是这个效果肯定是无法达到项目的需求。
一般这种应用商店类的项目,肯定是会要自定义提示框效果的安装卸载功能,而不是调用系统的安装程序。
那咱就要想法子实现静默安装、卸载咯。
网上有很多法子,如执行adb install 或pm install -r命令安装。但我想这并不可靠。记得之前有做过一个应用来执行linux命令,是通过RunTime来执行命令的。
后来发现其实并不靠谱,还不如直接用C代码来实现。
下面这种调用系统隐藏api接口来实现静默安装卸载,是比较大众靠谱的,实现自定义的提示界面。O(∩_∩)O~


3.系统隐藏的api

隐藏api,顾名思义,普通情况下肯定是调用不到的。翻翻源码\frameworks\base\core\java\android\content\pm目录下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目录,这样就不会报错了O(∩_∩)O~。

  • 作者项目里面用到了pm,所以把PackageManager.java以及涉及到的一些文件也拷贝过来了,不然eclipse报找不到PackageManager对象。结构如下:
    android 实现静默安装、卸载(图)_第1张图片
    (注:此处的包名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 {
    		Context cxt;
    		String appName;
    		String filename;
    		String pkname;
    
    		public MyPakcageInstallObserver(Context c, String appName,
    				 String filename,String packagename) {
    			this.cxt = c;
    			this.appName = appName;
    			this.filename = filename;
    			this.pkname = packagename;
    		}
    
    		@Override
    		public void packageInstalled(String packageName, int returnCode) {
    			Log.i(TAG, "returnCode = " + returnCode);// 返回1代表安装成功
                            if (returnCode == 1) {
                                //TODO
                            }
    			Intent it = new Intent();
    			it.setAction(CustomAction.INSTALL_ACTION);
    			it.putExtra("install_returnCode", returnCode);
    			it.putExtra("install_packageName", packageName);
    			it.putExtra("install_appName", appName); cxt.sendBroadcast(it);
    		}
    	}

    卸载回调接口同上。

  • 调用PackageManager.java隐藏方法,代码如下:

    /**
    	 * 静默安装
    	 * */
    	public static void autoInstallApk(Context context, String fileName,
    			String packageName, String APPName) {
    		Log.d(TAG, "jing mo an zhuang:" + packageName + ",fileName:" + fileName);
    		File file = new File(fileName);
    		int installFlags = 0;
    		if (!file.exists())
    			return;
    		installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
    		if (hasSdcard()) {
    			installFlags |= PackageManager.INSTALL_EXTERNAL;
    		}
    		PackageManager pm = context.getPackageManager();
    		try {
    			IPackageInstallObserver observer = new MyPakcageInstallObserver(
    					context, APPName, appId, fileName,packageName,type_name);
    			Log.i(TAG, "########installFlags:" + installFlags+"packagename:"+packageName);
    			pm.installPackage(Uri.fromFile(file), observer, installFlags,
    					packageName);
    		} catch (Exception e) {
    			
    		}
    
    	}

    卸载调用同上

很多码友联系,这里经常出错,现整理参考代码如下(下面代码有些格式问题)

package cn.thear;

import java.io.File;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Environment;
import android.os.RemoteException;
import android.util.Log;

public class ApkOperateManager {
	public static String TAG = "ApkOperateManager";

	/***安装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);
	}

	/**
	 * 静默安装
	 * */
	public static void installApkDefaul(Context context, String fileName,
			String packageName, String APPName, String appId, String type_name) {
		Log.d(TAG, "jing mo an zhuang:" + packageName + ",fileName:" + fileName
				+ ",type_name:" + type_name);
		File file = new File(fileName);
		int installFlags = 0;
		if (!file.exists())
			return;
		installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
		if (hasSdcard()) {
			installFlags |= PackageManager.INSTALL_EXTERNAL;
		}
		PackageManager pm = context.getPackageManager();
		// try {  try {
			IPackageInstallObserver observer = new MyPakcageInstallObserver(
					context, APPName, appId, fileName, packageName, type_name);
			Log.i(TAG, "########installFlags:" + installFlags + "packagename:"
					+ packageName);
			pm.installPackage(Uri.fromFile(file), observer, installFlags,
					packageName);
		} catch (Exception e) {
			((MarketApplication) context).setApp_detail_status(appId,
					MarketApplication.APP_STATUS_NOTEXIT);
		}

	}

	/* 静默卸载 */
	public static void uninstallApkDefaul(Context context, String action,
			String packageName) {
		PackageManager pm = context.getPackageManager();
		IPackageDeleteObserver observer = new MyPackageDeleteObserver(context,
				action, packageName);
		pm.deletePackage(packageName, observer, 0);
	}

	/* 静默卸载回调 */
             private static class MyPackageDeleteObserver extends
			IPackageDeleteObserver.Stub {
		Context cxt;
		String action;
		String pkname;

		public MyPackageDeleteObserver(Context c, String action, String pkname) {
			this.cxt = c;
			this.action = action;
			this.pkname = pkname;
		}

		@Override
public void packageDeleted(String packageName, int returnCode) {
			Log.d(TAG, "returnCode = " + returnCode + ",action:" + action
					+ "packageName:" + packageName + ",pkname:" + pkname);// 返回1代表卸载成功
			if (returnCode == 1) {//TODO 以下是删除数据库记录,只做参考
				/*SharedPreferences installedAPPInfo = cxt.getSharedPreferences(
						"installedAPPInfo", Context.MODE_WORLD_READABLE);
				if (installedAPPInfo.contains(packageName)) {
					String appId = installedAPPInfo.getString(packageName,
							"no this appId");
					((MarketApplication) cxt.getApplicationContext())
							.setApp_detail_status(appId,
									MarketApplication.APP_STATUS_NOTEXIT);
					installedAPPInfo.edit().remove(packageName).commit();
					ContentResolver conResolver = cxt.getContentResolver();
					conResolver.delete(InstalledAppInfo.CONTENT_URI,
							InstalledAppInfo.APP_PKNAME + " = " + "'" + pkname
									+ "'", null);
				}
				MarketApplication ma = ((MarketApplication) cxt
						.getApplicationContext());
				Log.e(TAG, "###packageDeleted###111size:"
						+ ma.getManagerLists().size());
				ma.removeManagerItem(pkname);
				ma.removeUpdateItem(pkname);
				Log.e(TAG, "##packageDeleted####22222size:"
						+ ma.getManagerLists().size());*/
			}
			
			
			Intent it = new Intent();
			it.setAction(action);
			it.putExtra("uninstall_returnCode", returnCode);
			cxt.sendBroadcast(it);
		}
	}

	/* 静默安装回调 */
	private static class MyPakcageInstallObserver extends
			IPackageInstallObserver.Stub {
		Context cxt;
		String appName;
		String appId;
		String filename;
		String pkname;
		String type_name;

		public MyPakcageInstallObserver(Context c, String appName,
				String appId, String filename, String packagename,
				String type_name) {
			this.cxt = c;
			this.appName = appName;
			this.appId = appId;
			this.filename = filename;
			this.pkname = packagename;
			this.type_name = type_name;
		}

		@Override
		public void packageInstalled(String packageName, int returnCode) {
			MarketApplication ma = ((MarketApplication) cxt
					.getApplicationContext());
			Log.i(TAG,
					"returnCode = " + returnCode + ","
							+ ma.getApp_detail_status(appId));// 返回1代表安装成功
			Intent it = new Intent();
			it.setAction(CustomAction.INSTALL_ACTION);
			it.putExtra("install_returnCode", returnCode);
			it.putExtra("install_packageName", packageName);
			it.putExtra("install_appName", appName);
			it.putExtra("install_appId", appId);
			if (returnCode == 1) {
				//ma.getAPPList();
				//ma.setManagerLists();
				if (ma.getApp_detail_status(appId) == MarketApplication.APP_STATUS_UPDATITNG) {
					ma.removeUpdateItem(pkname);
					cxt.sendBroadcast(it);
					return;
				}

				SharedPreferences installedAPPInfo = cxt.getSharedPreferences(
						"installedAPPInfo", Context.MODE_WORLD_READABLE);
				installedAPPInfo.edit().putString(packageName, appId).commit();

				// 保存信息到数据库
				if (appId != null && appName != null && pkname != null
						&& type_name != null) {
					ContentResolver conResolver = cxt.getContentResolver();
					ContentValues values = new ContentValues();
					values.put(InstalledAppInfo.APP_ID, appId);
					values.put(InstalledAppInfo.APP_NAME, appName);
					values.put(InstalledAppInfo.APP_PKNAME, pkname);
					values.put(InstalledAppInfo.APP_TYPENAME, type_name);
					Uri result = conResolver.insert(
							InstalledAppInfo.CONTENT_URI, values);
					Log.i(TAG,
							"#########install suscess...result:"
									+ result.toString());
				}
				ma.setApp_detail_status(appId,
						MarketApplication.APP_STATUS_INSTALLED);
			} 
			File f = new File(filename);
			if (f.exists()) {
				f.delete();
			}

			cxt.sendBroadcast(it);
		}
	}

	/**
	 * sd卡不存在
	 */
	public static final int NO_SDCARD = -1;

	/**
	 * 移动应用到SD Card
	 * 
	 * @param context
	 * @param pkname
	 * @return
	 */
	public static void movePackage(Context context, String pkname) {
		PackageManager pm = context.getPackageManager();
		MovePackageObserver mpo = new MovePackageObserver();
		pm.movePackage(pkname, mpo, PackageManager.INSTALL_EXTERNAL);
	}

	/**
	 * 移动应用的回调
	 */
	public static class MovePackageObserver extends IPackageMoveObserver.Stub {

		public MovePackageObserver() {
		}

		@Override
		public void packageMoved(String packageName, int returnCode)
				throws RemoteException {
			Log.i(TAG, "packagename:" + packageName + ",returnCode:"
					+ returnCode);
		}
	}

	/**
	 * 判断有无sd卡
	 * */
	public static boolean hasSdcard() {
		String status = Environment.getExternalStorageState();
		if (status.equals(Environment.MEDIA_MOUNTED)
				|| status.equals("/mnt/sdcard")) {
			Log.i(TAG, "has sdcard....");
			return true;
		} else {
			return false;
		}
	}
}





 自此,静默安装卸载代码实现。最后在AndroidMenifast.xml中要注册权限和添加为系统用户组,如果eclipse编译的话,并记得签名(不会的话戳这里):

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xxx.xxx.xxxx"   
    android:installLocation="internalOnly"
    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>

注:最后特别注意一点,因为下载的apk等只有rw----- root可读写权限,必须用个办法来给下载的apk赋权限,让系统级应用可以打开操作。这里作者是通过一个jni来调用C层接口,实现给指定的apk赋权限。然后执行安装apk过程

permission_change.cpp


#include <jni.h>
#include <string.h>

#include <android/log.h>
#include <sys/types.h> 
#include <sys/stat.h>
#include <stdio.h>


#define LOGI printf
#define LOGE printf

    static const char *classPathName = "com/utils/PermissionNative"; //此处包名视java层native包名而定
    typedef union{
        JNIEnv* env;
        void* venv;
     }UnionJNIEnvToVoid;
     static jboolean ChangePermission(const char* str)
     {    
     	    const char* p;
     	    char tmp_path[50];
     	    memset(tmp_path,0,sizeof(tmp_path));
     	    p = str+5;
          while(p < str+strlen(str))
          {
          	if(*p =='/')
          	{ 
          		memcpy(tmp_path,str,p-str);
          		if(chmod(tmp_path,0755) == -1)
          		{
          	 		 LOGI("chmod  %s failed!\n",tmp_path);		  
         	  	   return JNI_FALSE;//	  
       		  	}
       		  	LOGI("tmp_path_chmod = %s\n",tmp_path);		 
          	}
          	p++;
          }
          if(chmod(str,0755) == -1)
          	{  
          		  LOGI("chmod  %s failed!\n",str);		      		  
          		 return JNI_FALSE;//	  
          	}
          return JNI_TRUE;//
     } 

     static jboolean   PermissionChange(JNIEnv *env, jobject thiz,jstring path)
     {
          const char* str;
          str = env->GetStringUTFChars(path,false);
          if(str == NULL)
          {
          	  return JNI_FALSE;
          }
          if(ChangePermission(str))
          {
            env->ReleaseStringUTFChars(path,str);
            return JNI_TRUE;//JNI_FALSE
          } else {
          	  env->ReleaseStringUTFChars(path,str);
              return JNI_FALSE;//
          }
     }
   
    static JNINativeMethod methods[] = {
      {"native_permission_change", "(Ljava/lang/String;)Z", (void*)PermissionChange },
    };

    /*
     * Register several native methods for one class.
     */
    static int registerNativeMethods(JNIEnv* env, const char* className,
        JNINativeMethod* gMethods, int numMethods)
    {
        jclass clazz;
        clazz = env->FindClass(className);
        if (clazz == NULL) {
        	LOGI("Native registration unable to find class '%s'\n", className);
            return JNI_FALSE;
        }
        LOGI("FindClass succ\n");
        if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        	LOGI("RegisterNatives failed for '%s'\n", className);
            return JNI_FALSE;
        }
        LOGI("RegisterNatives succ\n");
        return JNI_TRUE;
    }

    /*
     * Register native methods for all classes we know about.
     *
     * returns JNI_TRUE on success.
     */
    static int registerNatives(JNIEnv* env)
    {
      if (!registerNativeMethods(env,classPathName,
                    methods, sizeof(methods) / sizeof(methods[0]))) {
          return JNI_FALSE;
      }
      LOGI("registerNatives succ\n");
      return JNI_TRUE;
    }

    // -------------------------------------------------------------------------

    /*
     * This is called by the VM when the shared library is first loaded.
     */



    jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        UnionJNIEnvToVoid uenv;
        uenv.venv = NULL;
        jint result = -1;
        JNIEnv* env = NULL;

        LOGI("JNI_OnLoad begin\n");

        if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        	LOGI("ERROR: GetEnv failed\n");
            goto bail;
        }
        LOGI("GetEnv succ\n");
        env = uenv.env;

        if (registerNatives(env) != JNI_TRUE) {
        	LOGI("ERROR: registerNatives failed\n");
            goto bail;
        }
        LOGI("registerNatives succ!");
        result = JNI_VERSION_1_4;

    bail:
        return result;
    }

Android.mk如下:

LOCAL_PATH :=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE :=false
LOCAL_MODULE_TAGS := eng
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
LOCAL_SRC_FILES :=permission_change.cpp
LOCAL_MODULE :=libpermission_change_jni

include $(BUILD_SHARED_LIBRARY)


4.拷贝apk

条件:

  1. 获取系统权限

  2. 拷贝apk到data/app

注:4.2和4.0上以测试通过,不过没有回调提示的,需要自己代码实现


5.效果图

最最后,眼见为实:附上效果图(注ddms截图有色差,不知道咋解决,有知道的请告之,万分感谢):

android 实现静默安装、卸载(图)_第2张图片


android 实现静默安装、卸载(图)_第3张图片


android 实现静默安装、卸载(图)_第4张图片


android 实现静默安装、卸载(图)_第5张图片


6.补充说明

遇到很多朋友实现静默安装时,来问很多各式各样的问题,这里我说明下:

我只是提供了一种我实现的方式,并不是个公共的模板,大家实现的时候肯定会遇到很多问题,有些编译不过,或者那些类找不到,参数不对等等,我也感到很无力。

这里我只是记录自己工作中实现时大的方向,并且所有相关的代码都在里面。如果有哪些模糊不明白的,欢迎大家来咨询;但是像一些编译问题我觉得大家还是自己解决,并且各个环境不一样,我也不一定能解决。

最后我只能很确切的保证,以上方式是绝对可行的。但是也有局限性,并不是所有平台都能通用的,只能是系统用户组的apk才能调用隐藏接口,并需要签名(不同平台的签名肯定是不一样的)。具体原因可以看我后面的一篇介绍签名的文章

android签名机制(1)——了解签名



你可能感兴趣的:(android,apk,安装,卸载,静默)