編輯:關於Android編程
最近好長時間都沒有寫blog了,主要是因為最近工作上的事以及下載Android源碼的事耽誤的(下載源碼這件事會在後續的blog中寫道,這個真的很有意義呀~~),那麼今天來寫點什麼呢?主要的靈感來自於早上看新聞看到一篇文章說有一款應用在後台中卸載用戶手機中的所有浏覽器的app,不會被用戶察覺,但是最後百度浏覽器還是用反偵察技術找到這個邪惡的應用然後將其告上法庭了。那麼我們就來看看怎麼能夠實現應用的靜態安裝和卸載呢?就是不讓用戶知道,下面就來一步一步的介紹一下實現步驟:
/* 安裝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來安裝程序了。
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 } } }
(關於這個調用系統隱藏的api接口,我們在之前講到如何獲取手機電量的一篇文章中介紹過了
http://blog.csdn.net/jiangwei0910410003/article/details/25994337)
安裝接口:
public abstract void installPackage(Uri packageURI,IPackageInstallObserver observer, int flags,String installerPackageName);卸載接口:
public abstract void deletePackage(String packageName,IPackageDeleteObserver observer, int flags);
package android.content.pm; /** * API for installation callbacks from the Package Manager. * @hide */ oneway interface IPackageInstallObserver { void packageInstalled(in String packageName, int returnCode); }
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+16KjujxzdHJvbmc+tMu0prXEsPzD+2FuZHJvaWQuY29udGVudC5wbdK7tqjSqrrN1LTC68S/wry94bm50rvWwjwvc3Ryb25nPqOssrvIu9S0wuvA77Hg0uu74czhyr7V0rK7tb1haWRsvdO/2qGj0rvH0LOv1LTC67Hg0uu/tMbrPGJyPgq0y7Sm09Ay1ta3vcq9yrXP1qO6PGJyPgoxLtaxvdPWu8ihSVBhY2thZ2VEZWxldGVPYnNlcnZlci5haWRsus1JUGFja2FnZXJJbnN0YWxsT2JzZXJ2ZXIuYWlkbKGiSVBhY2thZ2VNb3ZlT2JzZXJ2ZXIuYWlkbLXI0qrKudPDtcS907/ao6zIu7rzzai5/WJpbmRTZXJ2aWNlwLS6zc+1zbPBrL3Tt/7O8aOsyLu689axvdO199PDvdO/2ry0v8mjqNXi1tbDu9PQt73Kvdf31d/Du8rUuf2jrLK7uf3UrcDtyc/AtMu106a4w8rHv8nQ0LXEo6yz/bfHz7XNs8O709DV4rj2U2VydmljZcq1z9bV4rj2vdO/2qGj09DQ6MfztcS/ydLUye6+v8/Co6k8YnI+CjIu1/fV37TLtKa1xLe9t6jKx9axvdO/vbG0wcvUtMLrUGFja2FnZU1hbmFnZXIuamF2YbXIzsS8/rn9wLSjrLK7uf2/v7n9wLTWrrrzZWNsaXBzZbvhzOHKvtK70Km907/atO3O86OstavV4sDv1/fV37DRyc/D5sTHvLi49i5qYXZhzsS8/ra8t8W/1cHLo6zS8s6q08Oyu7W9o6zWu8rHzqrBy7Hg0uu5/bLFv72xtMHLxMfDtLbgzsS8/qGj1+688rWltcS+zcrH1rG907+9sbQ0uPbOxLz+vLS/yaO6PGJyPgpQYWNrYWdlTWFuYWdlci5qYXZhPGJyPgpJUGFja2FnZURlbGV0ZU9ic2VydmVyLmFpZGw8YnI+CklQYWNrYWdlckluc3RhbGxPYnNlcnZlci5haWRsPGJyPgpJUGFja2FnZU1vdmVPYnNlcnZlci5haWRsPGJyPgrIu7rzsNFQYWNrYWdlTWFuYWdlci5qYXZh1tCxqLXE0uyzo7XEvdO/2ra816LKzbX0vLS/yTxicj4KyrXP1rvYtfe907/ao6y0+sLryOfPwqO6PGJyPgo8L3A+CjxwPrCy17C1xLvYtfe907/ao7o8L3A+CjxwPjwvcD4KPHByZSBjbGFzcz0="brush: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中要注冊權限和添加為系統用戶組
...
這裡要注意的地方有兩個:第一個必須要添加:
android:sharedUserId="android.uid.system"
這個是將其應用注冊成系統用戶組中
還要添加安裝和卸載的權限:
好吧,貌似不是那麼的順利,出現一個安裝錯誤,其實這個信息去百度一下之後會找到很多的相關資料,因為這個問題有很多人遇到過,所以解決的方法也是很多的,就是需要將我們的應用簽名成系統級的即可,那麼我們該怎麼辦呢?
網上普遍的兩種方法:
第一種是:得到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)
然後進行編譯即可
其實我們在將第一種方法的時候有一個問題就是那三個簽名的文件到哪裡能搞到,這個嗎?可以到網上去搜索的,但是本人在下載la下來然後進行簽名,結果總是失敗,於是,我就放棄了從網上去下載,那麼還有什麼方法可以得到呢?好吧,還得去下載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)
下面我們再來看一下如何使用命令的方式來實現默認的安裝和卸載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中的配置:
也有:
android:sharedUserId="android.uid.system"這個在前面已經說過了,所以我們現在做的還是將其進行系統簽名,具體方法就不多說了,同上
下面是運行的結果圖:
Demo工程下載地址:
http://download.csdn.net/detail/jiangwei0910410003/7584429
下載之後的工程導入之後需要進行的幾步操作和前面的是一模一樣的,這裡就不做解釋了~~
總結:上面就介紹了實現靜態安裝和卸載的功能,其實還有其他的方法,比如我們可以將需要默認安裝的apk文件直接拷貝到目錄:
/data/app,因為我們知道所有用戶安裝的應用都是放在這個目錄中的,但是經過測試發現,默認安裝是沒有問題的,直接將其拷貝
即可,但是在進行卸載的時候有點問題,我們直接刪除apk文件的話,手機中還是有這個應用的圖標的,但是我們點擊打開的時候會
出現死機的情況,就相當於我們在Window系統中誤刪了一個文件之後打不開程序的結果一樣,所以這種卸載還是不靠譜的,不建議
使用,而且這種方式的話還需要獲取手機的root權限
待解決的問題:
1. 上面我們可以看到我們使用的是4.4版本進行簽名的,只能安裝到系統是4.4版本的機子上,這個有待考證,因為我們開發的應用不可能只能裝到指定的版本中,必須是所有的系統都能進行安裝(比如豌豆莢就可以),所以我就做了一個假設,是不是低版本的簽名文件進行系統簽名之後的apk可以安裝到高版本系統中,那麼這樣就好辦了,我們可以下載Android2.2(因為現在市面上最低的版本是2.2了)源碼,得到他的簽名文件,或者從網上搜索到這個版本的簽名文件(至今未找到,如果有找到的請分享,謝謝!!)
2. 還有一個問題就是上述的測試環境都是在手機或者模擬器都是root過了,有待考證的是沒有root的手機會不會安裝時出現問題~~
上述的blog是糾結了我幾個月的時間才解決的,所以我真心想說的一句話就是:做事千萬不要放棄,辦法總比問題多,一定要堅持~~,其實上面遇到的最大的問題就是在下載源碼過程(沒有技術可言,就是考驗我們的耐心,同時網絡也是一個重要的原因,這裡由於某些原因,我下載的是4.4版本的源碼,共13G,這裡就不公開下載地址了,如果真心需要請留言,我可以共享一下~~),還有就是編譯過程,我花費了一周的時間,因為在這個過程中可以說是千難萬險,什麼問題都有,我重新編譯好幾次~~
工廠方法模式,往往是設計模式初學者入門的模式,的確,有人稱之為最為典型最具啟發效果的模式。android中用到了太多的工廠類,其中有用工廠方法模式的,當然也有很多工廠並不
很久前也寫過一篇Android數據庫操作相關內容。在正式項目中,我們通常會使用數據庫開源框架如GreenDao來對數據庫進行操作。感覺很久沒有直接使用Sql語句了,這幾天
Java中的內存洩漏java內存洩漏大家都不陌生了,簡單粗俗的講,就是該被釋放的對象沒有釋放,一直被某個或某些實例所持有卻不再被使用導致 GC 不能回收。在Java中,內
一、引子 學過Android的同學都知道,對話框的重要程度非常高,任何一款 app幾乎都離不開對話框,值得慶幸的是,對話框的運用在Android中還是相對比較容易的。雖然