android 实现静默安装、卸载(图)
android中应用的安装卸载,大家(用android设备的)肯定不陌生。这里就来浅谈android应用的安装、卸载的实现方式。
1.系统安装程序
android自带了一个安装程序---/system/app/PackageInstaller.apk.大多数情况下,我们手机上安装应用都是通过这个apk来安装的。代码使用也非常简单:
/*安装apk*/publicstaticvoidinstallApk(Contextcontext,StringfileName){Intentintent=newIntent();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*/publicstaticvoiduninstallApk(Contextcontext,StringpackageName){Uriuri=Uri.parse("package:"+packageName);Intentintent=newIntent(Intent.ACTION_DELETE,uri);context.startActivity(intent);}
通过发一个Intent,把应用所在的路径封装整uri.之后默认启动了PackageInstaller.apk来安装程序了。
但是此种情况下,仅仅是个demo而已,很难达到开发者的需求。如:
界面不好
什么时候安装完了,卸载完了呢?
为了达到自己的需求,相信很多人都会接着来监听系统安装卸载的广播,继续接下来的代码逻辑。
2.监听系统发出的安装广播
在安装和卸载完后,android系统会发一个广播
android.intent.action.PACKAGE_ADDED(安装)
android.intent.action.PACKAGE_REMOVED(卸载)
咱们就监听这广播,来做响应的逻辑处理。实现代码:
publicclassMonitorSysReceiverextendsBroadcastReceiver{@OverridepublicvoidonReceive(Contextcontext,Intentintent){//接收安装广播if(intent.getAction().equals("android.intent.action.PACKAGE_ADDED")){//TODO}//接收卸载广播if(intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")){//TODO}}}
AndroidMenifast.xml里配置:
<receiverandroid:name=".MonitorSysReceiver"><intent-filter><actionandroid:name="android.intent.action.PACKAGE_ADDED"/><actionandroid: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声明。调用的安装下载接口如下:
publicabstractvoidinstallPackage(UripackageURI,IPackageInstallObserverobserver,intflags,StringinstallerPackageName);
publicabstractvoiddeletePackage(StringpackageName,IPackageDeleteObserverobserver,intflags);
并且都是抽象方法,需要咱们实现。
看参数里IPackageInstallObserver observer一个aidl回调通知接口,当前目录中找到这接口:
packageandroid.content.pm;/***APIforinstallationcallbacksfromthePackageManager.*@hide*/onewayinterfaceIPackageInstallObserver{voidpackageInstalled(inStringpackageName,intreturnCode);}
好吧,这里有现成的干货,咱拿过来直接用呗(当然如果没有源码的那就算了,那能实现的只是demo)。具体步骤:
从源码中拷贝要使用的aidl回调接口:IPackageInstallObserver.aidl、IPackageDeleteObserver.aidl当然完全可以拷贝整个pm目录,这样就不会报错了O(∩_∩)O~。
作者项目里面用到了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中报的异常的接口都注释掉即可
实现回调接口,代码如下
classMyPakcageInstallObserverextendsIPackageInstallObserver.Stub{Contextcxt;StringappName;Stringfilename;Stringpkname;publicMyPakcageInstallObserver(Contextc,StringappName,Stringfilename,Stringpackagename){this.cxt=c;this.appName=appName;this.filename=filename;this.pkname=packagename;}@OverridepublicvoidpackageInstalled(StringpackageName,intreturnCode){Log.i(TAG,"returnCode="+returnCode);//返回1代表安装成功if(returnCode==1){//TODO}Intentit=newIntent();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隐藏方法,代码如下:
/***静默安装**/publicstaticvoidautoInstallApk(Contextcontext,StringfileName,StringpackageName,StringAPPName){Log.d(TAG,"jingmoanzhuang:"+packageName+",fileName:"+fileName);Filefile=newFile(fileName);intinstallFlags=0;if(!file.exists())return;installFlags|=PackageManager.INSTALL_REPLACE_EXISTING;if(hasSdcard()){installFlags|=PackageManager.INSTALL_EXTERNAL;}PackageManagerpm=context.getPackageManager();try{IPackageInstallObserverobserver=newMyPakcageInstallObserver(context,APPName,appId,fileName,packageName,type_name);Log.i(TAG,"########installFlags:"+installFlags+"packagename:"+packageName);pm.installPackage(Uri.fromFile(file),observer,installFlags,packageName);}catch(Exceptione){}}
卸载调用同上
很多码友联系,这里经常出错,现整理参考代码如下(下面代码有些格式问题):
packagecn.thear;importjava.io.File;importandroid.content.ContentResolver;importandroid.content.ContentValues;importandroid.content.Context;importandroid.content.Intent;importandroid.content.SharedPreferences;importandroid.content.pm.IPackageDeleteObserver;importandroid.content.pm.IPackageInstallObserver;importandroid.content.pm.IPackageMoveObserver;importandroid.content.pm.PackageManager;importandroid.net.Uri;importandroid.os.Environment;importandroid.os.RemoteException;importandroid.util.Log;publicclassApkOperateManager{publicstaticStringTAG="ApkOperateManager";/***安装apk*/publicstaticvoidinstallApk(Contextcontext,StringfileName){Intentintent=newIntent();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*/publicstaticvoiduninstallApk(Contextcontext,StringpackageName){Uriuri=Uri.parse("package:"+packageName);Intentintent=newIntent(Intent.ACTION_DELETE,uri);context.startActivity(intent);}/***静默安装**/publicstaticvoidinstallApkDefaul(Contextcontext,StringfileName,StringpackageName,StringAPPName,StringappId,Stringtype_name){Log.d(TAG,"jingmoanzhuang:"+packageName+",fileName:"+fileName+",type_name:"+type_name);Filefile=newFile(fileName);intinstallFlags=0;if(!file.exists())return;installFlags|=PackageManager.INSTALL_REPLACE_EXISTING;if(hasSdcard()){installFlags|=PackageManager.INSTALL_EXTERNAL;}PackageManagerpm=context.getPackageManager();//try{try{IPackageInstallObserverobserver=newMyPakcageInstallObserver(context,APPName,appId,fileName,packageName,type_name);Log.i(TAG,"########installFlags:"+installFlags+"packagename:"+packageName);pm.installPackage(Uri.fromFile(file),observer,installFlags,packageName);}catch(Exceptione){((MarketApplication)context).setApp_detail_status(appId,MarketApplication.APP_STATUS_NOTEXIT);}}/*静默卸载*/publicstaticvoiduninstallApkDefaul(Contextcontext,Stringaction,StringpackageName){PackageManagerpm=context.getPackageManager();IPackageDeleteObserverobserver=newMyPackageDeleteObserver(context,action,packageName);pm.deletePackage(packageName,observer,0);}/*静默卸载回调*/privatestaticclassMyPackageDeleteObserverextendsIPackageDeleteObserver.Stub{Contextcxt;Stringaction;Stringpkname;publicMyPackageDeleteObserver(Contextc,Stringaction,Stringpkname){this.cxt=c;this.action=action;this.pkname=pkname;}@OverridepublicvoidpackageDeleted(StringpackageName,intreturnCode){Log.d(TAG,"returnCode="+returnCode+",action:"+action+"packageName:"+packageName+",pkname:"+pkname);//返回1代表卸载成功if(returnCode==1){//TODO以下是删除数据库记录,只做参考/*SharedPreferencesinstalledAPPInfo=cxt.getSharedPreferences("installedAPPInfo",Context.MODE_WORLD_READABLE);if(installedAPPInfo.contains(packageName)){StringappId=installedAPPInfo.getString(packageName,"nothisappId");((MarketApplication)cxt.getApplicationContext()).setApp_detail_status(appId,MarketApplication.APP_STATUS_NOTEXIT);installedAPPInfo.edit().remove(packageName).commit();ContentResolverconResolver=cxt.getContentResolver();conResolver.delete(InstalledAppInfo.CONTENT_URI,InstalledAppInfo.APP_PKNAME+"="+"'"+pkname+"'",null);}MarketApplicationma=((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());*/}Intentit=newIntent();it.setAction(action);it.putExtra("uninstall_returnCode",returnCode);cxt.sendBroadcast(it);}}/*静默安装回调*/privatestaticclassMyPakcageInstallObserverextendsIPackageInstallObserver.Stub{Contextcxt;StringappName;StringappId;Stringfilename;Stringpkname;Stringtype_name;publicMyPakcageInstallObserver(Contextc,StringappName,StringappId,Stringfilename,Stringpackagename,Stringtype_name){this.cxt=c;this.appName=appName;this.appId=appId;this.filename=filename;this.pkname=packagename;this.type_name=type_name;}@OverridepublicvoidpackageInstalled(StringpackageName,intreturnCode){MarketApplicationma=((MarketApplication)cxt.getApplicationContext());Log.i(TAG,"returnCode="+returnCode+","+ma.getApp_detail_status(appId));//返回1代表安装成功Intentit=newIntent();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;}SharedPreferencesinstalledAPPInfo=cxt.getSharedPreferences("installedAPPInfo",Context.MODE_WORLD_READABLE);installedAPPInfo.edit().putString(packageName,appId).commit();//保存信息到数据库if(appId!=null&&appName!=null&&pkname!=null&&type_name!=null){ContentResolverconResolver=cxt.getContentResolver();ContentValuesvalues=newContentValues();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);Uriresult=conResolver.insert(InstalledAppInfo.CONTENT_URI,values);Log.i(TAG,"#########installsuscess...result:"+result.toString());}ma.setApp_detail_status(appId,MarketApplication.APP_STATUS_INSTALLED);}Filef=newFile(filename);if(f.exists()){f.delete();}cxt.sendBroadcast(it);}}/***sd卡不存在*/publicstaticfinalintNO_SDCARD=-1;/***移动应用到SDCard**@paramcontext*@parampkname*@return*/publicstaticvoidmovePackage(Contextcontext,Stringpkname){PackageManagerpm=context.getPackageManager();MovePackageObservermpo=newMovePackageObserver();pm.movePackage(pkname,mpo,PackageManager.INSTALL_EXTERNAL);}/***移动应用的回调*/publicstaticclassMovePackageObserverextendsIPackageMoveObserver.Stub{publicMovePackageObserver(){}@OverridepublicvoidpackageMoved(StringpackageName,intreturnCode)throwsRemoteException{Log.i(TAG,"packagename:"+packageName+",returnCode:"+returnCode);}}/***判断有无sd卡**/publicstaticbooleanhasSdcard(){Stringstatus=Environment.getExternalStorageState();if(status.equals(Environment.MEDIA_MOUNTED)||status.equals("/mnt/sdcard")){Log.i(TAG,"hassdcard....");returntrue;}else{returnfalse;}}}
自此,静默安装卸载代码实现。最后在AndroidMenifast.xml中要注册权限和添加为系统用户组,如果eclipse编译的话,并记得签名(不会的话戳这里):
<manifestxmlns: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-sdkandroid:minSdkVersion="4"/><uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.INSTALL_PACKAGES"/><uses-permissionandroid:name="android.permission.DELETE_PACKAGES"/><uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permissionandroid: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>#defineLOGIprintf#defineLOGEprintfstaticconstchar*classPathName="com/utils/PermissionNative";//此处包名视java层native包名而定typedefunion{JNIEnv*env;void*venv;}UnionJNIEnvToVoid;staticjbooleanChangePermission(constchar*str){constchar*p;chartmp_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%sfailed!\n",tmp_path);returnJNI_FALSE;//}LOGI("tmp_path_chmod=%s\n",tmp_path);}p++;}if(chmod(str,0755)==-1){LOGI("chmod%sfailed!\n",str);returnJNI_FALSE;//}returnJNI_TRUE;//}staticjbooleanPermissionChange(JNIEnv*env,jobjectthiz,jstringpath){constchar*str;str=env->GetStringUTFChars(path,false);if(str==NULL){returnJNI_FALSE;}if(ChangePermission(str)){env->ReleaseStringUTFChars(path,str);returnJNI_TRUE;//JNI_FALSE}else{env->ReleaseStringUTFChars(path,str);returnJNI_FALSE;//}}staticJNINativeMethodmethods[]={{"native_permission_change","(Ljava/lang/String;)Z",(void*)PermissionChange},};/**Registerseveralnativemethodsforoneclass.*/staticintregisterNativeMethods(JNIEnv*env,constchar*className,JNINativeMethod*gMethods,intnumMethods){jclassclazz;clazz=env->FindClass(className);if(clazz==NULL){LOGI("Nativeregistrationunabletofindclass'%s'\n",className);returnJNI_FALSE;}LOGI("FindClasssucc\n");if(env->RegisterNatives(clazz,gMethods,numMethods)<0){LOGI("RegisterNativesfailedfor'%s'\n",className);returnJNI_FALSE;}LOGI("RegisterNativessucc\n");returnJNI_TRUE;}/**Registernativemethodsforallclassesweknowabout.**returnsJNI_TRUEonsuccess.*/staticintregisterNatives(JNIEnv*env){if(!registerNativeMethods(env,classPathName,methods,sizeof(methods)/sizeof(methods[0]))){returnJNI_FALSE;}LOGI("registerNativessucc\n");returnJNI_TRUE;}//-------------------------------------------------------------------------/**ThisiscalledbytheVMwhenthesharedlibraryisfirstloaded.*/jintJNI_OnLoad(JavaVM*vm,void*reserved){UnionJNIEnvToVoiduenv;uenv.venv=NULL;jintresult=-1;JNIEnv*env=NULL;LOGI("JNI_OnLoadbegin\n");if(vm->GetEnv(&uenv.venv,JNI_VERSION_1_4)!=JNI_OK){LOGI("ERROR:GetEnvfailed\n");gotobail;}LOGI("GetEnvsucc\n");env=uenv.env;if(registerNatives(env)!=JNI_TRUE){LOGI("ERROR:registerNativesfailed\n");gotobail;}LOGI("registerNativessucc!");result=JNI_VERSION_1_4;bail:returnresult;}
Android.mk如下:
LOCAL_PATH:=$(callmy-dir)include$(CLEAR_VARS)LOCAL_PRELINK_MODULE:=falseLOCAL_MODULE_TAGS:=engLOCAL_LDLIBS+=-L$(SYSROOT)/usr/lib-llogLOCAL_SRC_FILES:=permission_change.cppLOCAL_MODULE:=libpermission_change_jniinclude$(BUILD_SHARED_LIBRARY)
4.拷贝apk
条件:
获取系统权限
拷贝apk到data/app
注:4.2和4.0上以测试通过,不过没有回调提示的,需要自己代码实现
5.效果图
最最后,眼见为实:附上效果图(注ddms截图有色差,不知道咋解决,有知道的请告之,万分感谢):
6.补充说明
遇到很多朋友实现静默安装时,来问很多各式各样的问题,这里我说明下:
我只是提供了一种我实现的方式,并不是个公共的模板,大家实现的时候肯定会遇到很多问题,有些编译不过,或者那些类找不到,参数不对等等,我也感到很无力。
这里我只是记录自己工作中实现时大的方向,并且所有相关的代码都在里面。如果有哪些模糊不明白的,欢迎大家来咨询;但是像一些编译问题我觉得大家还是自己解决,并且各个环境不一样,我也不一定能解决。
最后我只能很确切的保证,以上方式是绝对可行的。但是也有局限性,并不是所有平台都能通用的,只能是系统用户组的apk才能调用隐藏接口,并需要签名(不同平台的签名肯定是不一样的)。具体原因可以看我后面的一篇介绍签名的文章
android签名机制(1)——了解签名
更多相关文章
- android中九宫格布局的实现
- Android之——手机黑名单的实现
- 【Tech-Android-Other】android中的Parcelable的实现
- [转]Android(安卓)实现TextView中文字链接的方式
- Linux通过shell脚本实现JDK版本之间的快速切换
- Linux通过shell脚本实现JDK版本之间的快速切换
- Android自动化测试之环境搭建(二)
- android 日常 (十六)
- Android(安卓)如何使用GPU硬件加速