一迈着、問題場景
最近在做Launcher,想要實(shí)現(xiàn)一個(gè)效果就是孕蝉,根據(jù)默認(rèn)配置文件把設(shè)定好的Widget在一開始就綁定到桌面上呐馆,但發(fā)現(xiàn)網(wǎng)上大多數(shù)的教程都是讓人通過Intent去調(diào)用系統(tǒng)的對(duì)話框风科,然后選擇widget添加斯议,這并不是我想要的效果产捞。
二、問題分析
Launcher3中的關(guān)鍵代碼:
void?addAppWidgetFromDrop(PendingAddWidgetInfo info,longcontainer,longscreenId,
int[]?cell,int[]?span,int[]?loc)?{
if(LauncherLog.DEBUG)?{
LauncherLog.d(TAG,"addAppWidgetFromDrop:?info?=?"+?info?+",?container?=?"+ container?+",?screenId?=?"
+?screenId);
}
resetAddInfo();
mPendingAddInfo.container?=?info.container?=?container;
mPendingAddInfo.screenId?=?info.screenId?=?screenId;
mPendingAddInfo.dropPos?=?loc;
mPendingAddInfo.minSpanX?=?info.minSpanX;
mPendingAddInfo.minSpanY?=?info.minSpanY;
if(cell?!=null)?{
mPendingAddInfo.cellX?=?cell[0];
mPendingAddInfo.cellY?=?cell[1];
}
if(span?!=null)?{
mPendingAddInfo.spanX?=?span[0];
mPendingAddInfo.spanY?=?span[1];
}
AppWidgetHostView?hostView?=?info.boundWidget;
intappWidgetId;
if(hostView?!=null)?{
appWidgetId?=?hostView.getAppWidgetId();
addAppWidgetImpl(appWidgetId,?info,?hostView,?info.info);
}else{
//?In?this?case,?we?either?need?to?start?an?activity?to?get?permission?to?bind
//?the?widget,?or?we?need?to?start?an?activity?to?configure?the?widget,?or?both.
appWidgetId?=?getAppWidgetHost().allocateAppWidgetId();
Bundle?options?=?info.bindOptions;
booleansuccess?=false;
mAppWidgetManager.setBindAppWidgetPermission(this.getPackageName(),true);//txk?add
if(options?!=null)?{
success?=?mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
info.componentName,?options);
}else{
success?=?mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
info.componentName);
}
if(success)?{
addAppWidgetImpl(appWidgetId,?info,null,?info.info);
}else{
mPendingAddWidgetInfo?=?info.info;
Intent?intent?=newIntent(AppWidgetManager.ACTION_APPWIDGET_BIND);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,?appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER,?info.componentName);
//?TODO:?we?need?to?make?sure?that?this?accounts?for?the?options?bundle.
//?intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS,?options);
startActivityForResult(intent,?REQUEST_BIND_APPWIDGET);
}
}
}
上面的關(guān)鍵代碼:
int?newWidgetId?=?mAppWidgetHost.allocateAppWidgetId();
boolean?bindResult?=?mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId,?info.provider);
上面的bindAppWidgetIdIfAllowed正常一開始都會(huì)返回false哼御,原因是沒有進(jìn)行綁定的權(quán)限坯临。所以就算將該AppWidget添加到桌面上,也將是失活的恋昼,沒辦法接受到應(yīng)用的通知看靠。那有什么辦法解決呢?
三液肌、解決方案
其實(shí)當(dāng)?shù)谝淮翁砑觲idget的時(shí)候都會(huì)走到這里挟炬,檢查該Launcher應(yīng)用是否在系統(tǒng)白名單中,如果是的嗦哆,則直接bind成功谤祖,否則彈出一個(gè)提示框讓用戶授權(quán),否則直接返回false老速。
但現(xiàn)在我們想要讓代碼不走到這里粥喜,就得在mAppWidgetManager.bindAppWidgetIdIfAllowed(……)之前提前授權(quán)就好了¢偃可以添加一行授權(quán)代碼mAppWidgetManager.setBindAppWidgetPermission(this.getPackageName(),?true);
但因?yàn)閟etBindAppWidgetPermission是系統(tǒng)隱藏的额湘,所以必須要進(jìn)行反射調(diào)用才行但是需要在Launcher3的AndroidManifest.xml里添加相應(yīng)權(quán)限
而這個(gè)權(quán)限是系統(tǒng)級(jí)權(quán)限秕铛,所以還需要把Launcher3改為系統(tǒng)級(jí)應(yīng)用。
四缩挑、如何把應(yīng)用變成系統(tǒng)應(yīng)用
第一個(gè)方法簡單點(diǎn),不過需要在Android系統(tǒng)源碼的環(huán)境下用make來編譯:
1鬓梅、在應(yīng)用程序的AndroidManifest.xml中的manifest節(jié)點(diǎn)中加入android:sharedUserId="android.uid.system"這個(gè)屬性供置。
2、修改Android.mk文件绽快,加入LOCAL_CERTIFICATE?:=?platform這一行
3芥丧、使用mm命令來編譯,生成的apk就有修改系統(tǒng)時(shí)間的權(quán)限了坊罢。
第二個(gè)方法麻煩點(diǎn)续担,不過不用開虛擬機(jī)跑到源碼環(huán)境下用make來編譯:
1、同上活孩,加入android:sharedUserId="android.uid.system"這個(gè)屬性物遇。
2、使用eclipse編譯出apk文件憾儒,但是這個(gè)apk文件是不能用的询兴。
3、用壓縮軟件打開apk文件起趾,刪掉META-INF目錄下的CERT.SF和CERT.RSA兩個(gè)文件诗舰。
4、使用目標(biāo)系統(tǒng)的platform密鑰來重新給apk文件簽名训裆。這步比較麻煩眶根,首先找到密鑰文件,在我的Android源碼目錄中的位置是"build\target\product\security"边琉,下面的platform.pk8和platform.x509.pem兩個(gè)文件属百。然后用Android提供的Signapk工具來簽名,signapk的源代碼是在"build\tools\signapk"下艺骂,用法為"signapk?platform.x509.pem?platform.pk8?input.apk?output.apk"诸老,文件名最好使用絕對(duì)路徑防止找不到,也可以修改源代碼直接使用钳恕。
這兩個(gè)方法得到的apk是一樣的别伏。
五忧额、原理分析
首先加入android:sharedUserId="android.uid.system"這個(gè)屬性厘肮。通過Shared User id,擁有同一個(gè)User id的多個(gè)APK可以配置成運(yùn)行在同一個(gè)進(jìn)程中。那么把程序的UID配成android.uid.system睦番,也就是要讓程序運(yùn)行在系統(tǒng)進(jìn)程中类茂,這樣就有權(quán)限來修改系統(tǒng)時(shí)間了耍属。
只是加入U(xiǎn)ID還不夠,如果這時(shí)候安裝APK的話發(fā)現(xiàn)無法安裝巩检,提示簽名不符厚骗,原因是程序想要運(yùn)行在系統(tǒng)進(jìn)程中還要有目標(biāo)系統(tǒng)的platform.?key,就是上面第二個(gè)方法提到的platform.pk8和platform.x509.pem兩個(gè)文件兢哭。用這兩個(gè)key簽名后apk才真正可以放入系統(tǒng)進(jìn)程中领舰。第一個(gè)方法中加入LOCAL_CERTIFICATE?:=?platform其實(shí)就是用這兩個(gè)key來簽名。
這也有一個(gè)問題迟螺,就是這樣生成的程序只有在原始的Android系統(tǒng)或者是自己編譯的系統(tǒng)中才可以用冲秽,因?yàn)檫@樣的系統(tǒng)才可以拿到platform.pk8和platform.x509.pem兩個(gè)文件。要是別家公司做的Android上連安裝都安裝不了矩父。試試原始的Android中的key來簽名锉桑,程序在模擬器上運(yùn)行OK,不過放到G3上安裝直接提示"Package?...?has?no?signatures?that?match?those?in?shared?user?android.uid.system"窍株,這樣也是保護(hù)了系統(tǒng)的安全民轴。
最后還說下,這個(gè)android:sharedUserId屬性不只可以把a(bǔ)pk放到系統(tǒng)進(jìn)程中球订,也可以配置多個(gè)APK運(yùn)行在一個(gè)進(jìn)程中杉武,這樣可以共享數(shù)據(jù),應(yīng)該會(huì)很有用的辙售。
六轻抱、提示
文件位置:可以在platform/build/target/product/security/中找到platform.pk8 platform.x509.pem等簽名文件,對(duì)應(yīng)不同的權(quán)限旦部。
signapk.jar:由/platform/build/tools/signapk/編譯產(chǎn)出,可以在/out/host/linux-x86/framework/中找到祈搜。
簽名:
java?-jar?signapk.jar?platform.x509.pem?platform.pk8?MyDemo.apk?MyDemo_signed.apk?得到具有對(duì)應(yīng)權(quán)限的APK
優(yōu)化APK:
zipalign?-v?4?MyDemo_signed.apk?MyDemo_new.apk
查循APK是否優(yōu)化過:
zipalign?-c?-v?4?MyDemo.apk