一、訪問隱藏的API方式進行靜態的默認安裝和卸載
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而已,很難達到開發者的需求。如:
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 } } }
AndroidManifest.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>
到此,確實安裝卸載的整體流程都知道了,但是這個效果肯定是無法達到項目的需求。
一般這種應用商店類(豌豆莢)的項目,肯定是會要自定義提示框效果的安裝卸載功能,而不是調用系統的安裝程序。
那咱就要想法子實現靜默安裝、卸載咯。
下面這種調用系統隱藏api接口來實現靜默安裝卸載,是比較大眾靠譜的,實現自定義的提示界面
2.系統隱藏的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目
錄,這樣就不會報錯了。
作者項目里面用到了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這三個文件進行簽名:簽名的命令很簡單:
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源
碼,編譯之后可以在指定的目錄中找到這三個文件
但是還沒有完,不管是第一種方法還是第二種方法,我在試驗的過程中還是失敗,原因是什么呢?因為我們如果用這三個文件進行
簽名或者把工程放到源碼中進行編譯的話,都必須要注意一點:就是簽名的文件的系統版本必須和安裝到設備的系統的版本一樣,
說白一點:現在假如簽名的那三個文件是從下載的編譯之后的源碼中得到的,那么這三個文件的版本就是這個Android源碼的版本:
比如是4.4版本的,那么我們用這三個文件進行簽名之后的apk包只能安裝到系統版本為4.4的機子上,同理如果我們將工程直接放到
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中導出一個未簽名的包,這個很簡單了,這里就不介紹了~~
運行的結果:
因為手頭上沒有4.4系統的機子,又不想刷機,所以就用模擬器了。在這個過程中安裝和卸載可能要一段時間,所以要等待一會,不要着急~~
那么我們就介紹了使用隱藏的api來進行默認的安裝和卸載app,
Demo工程下載地址:
http://download.csdn.net/detail/jiangwei0910410003/7584423
導入工程之后,需要做的幾步:
在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"
這個在前面已經說過了,所以我們現在做的還是將其進行系統簽名,具體方法就不多說了,同上
下面是運行的結果圖:
Demo工程下載地址:http://download.csdn.net/detail/jiangwei0910410003/7584429
下載之后的工程導入之后需要進行的幾步操作和前面的是一模一樣的,這里就不做解釋了~~
但是我們從上面的方法看到有一個弊端,還是需要系統簽名,這個對於一般app來說是個限制,現在機型很多,系統的簽名文件也都不一樣,所以很難做到這種方式能夠適配到所有的機型。
那么還有另外的一種方法:
2、不需要簽名文件,但是需要root權限
這種方式很簡單和上面的沒有太大的區別,就是將上面的命令放到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 + "\n"); os.writeBytes("exit\n"); 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文件實現安裝和卸載
這個方式其實很簡單,就是直接拷貝和刪除文件即可。我們知道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、卸載操作
卸載也很簡單,我需要刪除/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的手機會不會安裝時出現問題~~
上述的blog是糾結了我幾個月的時間才解決的,所以我真心想說的一句話就是:做事千萬不要放棄,辦法總比問題多,一定要堅持
~~,其實上面遇到的最大的問題就是在下載源碼過程(沒有技術可言,就是考驗我們的耐心,同時網絡也是一個重要的原因,這里由
於某些原因,我下載的是4.4版本的源碼,共13G,這里就不公開下載地址了,如果真心需要請留言,我可以共享一下~~),還有就是
編譯過程,我花費了一周的時間,因為在這個過程中可以說是千難萬險,什么問題都有,我重新編譯好幾次~~
好吧,這里有現成的干貨,咱拿過來直接用唄(當然如果沒有源碼的那就算了,那能實現的只是demo)。具體步驟:
從源碼中拷貝要使用的aidl回調接口:IPackageInstallObserver.aidl、IPackageDeleteObserver.aidl當然完全可以拷貝整個pm目
因為手頭上沒有4.4系統的機子,又不想刷機,所以就用模擬器了。在這個過程中安裝和卸載可能要一段時間,所以要等待一會,不要着急~~
那么我們就介紹了使用隱藏的api來進行默認的安裝和卸載app,
Demo工程下載地址:
http://download.csdn.net/detail/jiangwei0910410003/7584423
導入工程之后,需要做的幾步:
在SD卡中拷貝一個UC.apk到baidu文件夾(需要新建的)
導出未進行簽名的包
下載簽名的工具:
http://download.csdn.net/detail/jiangwei0910410003/7584441
按照上面的簽名步驟進行操作
然后找到一個系統是4.4版本的測試機或者模擬器進行安裝包(因為我的簽名文件的版本是4.4)
轉載請注明:尼古拉斯.趙四 » Android中實現靜態的默認安裝和卸載應用