Rayland
主板雖然作為一塊基於Android
的工控板,但是很多設備廠商並不想讓用戶看到Android系統
信息。所以APK
默認設置為開機啟動項、img
去除了Android
頭部和底部菜單。但是隨之帶來了APK
更新的問題,傳統的插入u盤
,sd卡
手動安裝新版本APK
的方式已經不夠用了。所以我們需要點自動的東西。
App內檢測更新新版本APK
檢測新版本APK
我們使用 四大組件之一的BroadcastReceiver
來檢測 sd卡
或是u盤設備
的接入。
public class StorageMountListener extends BroadcastReceiver{
@Override
public void onReceive(final Context context, Intent intent) {
if(intent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED)){
// 獲取插入設備路徑
String path = intent.getData().getPath();
// 檢測路徑是否有新版本APK
ApkUpdateUtils.getInstance().checkLocalUpdateAtBackground(context, path);
}
}
}
ApkUpdateUtils.java
/**
* 后台檢查指定目錄下是否有新版本的APK,有則提示安裝
* @param context 上下文
* @param path 需要檢查的目錄
*/
public void checkLocalUpdateAtBackground(final Context context, final String path){
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
// 檢查指定目錄下是否存在高版本的更新包,並返回結果
File apkFile = findUpdatePackage(context, path);
if(apkFile == null){
return;
}
File msg = new File(apkFile.getParent(), apkFile.getName().replace(".apk", ".txt"));
String description = readStringFile(msg);
Intent intent = new Intent(context, UpdateActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 新版本apk 路徑
intent.putExtra("apk", apkFile.getAbsolutePath());
// 新版本apk 描述信息
intent.putExtra("description", description);
context.startActivity(intent);
}
});
}
/**
* 檢查指定目錄下是否存在高版本的更新包,並返回結果
* @param path 檢查目錄
* @return APK文件
*/
public File findUpdatePackage(Context context, String path) {
File parent = new File(path);
if(!parent.exists() || parent.isFile()){
return null;
}
File[] apks = parent.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".apk");
}
});
if(apks == null || apks.length == 0){
return null;
}
try {
/**
* 通過 build.gradle 中的 versionCode 來判斷
* 每次版本更新后 修改versionCode
*/
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
File apkFile = null;
int versionCode = 0;
for(File apk : apks){
PackageInfo apkInfo = packageManager.getPackageArchiveInfo(apk.getAbsolutePath(), 0);
if(packageInfo.packageName.equals(apkInfo.packageName) && packageInfo.versionCode < apkInfo.versionCode){
if(apkFile == null){
apkFile = apk;
versionCode = apkInfo.versionCode;
}else{
if(versionCode < apkInfo.versionCode){
apkFile = apk;
versionCode = apkInfo.versionCode;
}
}
}
}
return apkFile;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 將文件內容讀取成String
* @param file 要讀取的文件
* @return 文件內容
*/
public String readStringFile(File file){
StringBuilder builder = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"));
String line;
while((line = br.readLine())!=null){
builder.append(line);
builder.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return builder.toString();
}
新版本更新提示
UpdateActivity.java
/**
* 顯示更新提示對話框
*/
private void showUpdateMsgDialog(final Context context, final String apk, String descrption ){
PackageInfo apkInfo = getPackageManager().getPackageArchiveInfo(apk, 0);
AlertDialog updateMsgDialog = new AlertDialog.Builder(context).create();
updateMsgDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
});
updateMsgDialog.setTitle("檢測到新版本"+apkInfo.versionName);
updateMsgDialog.setMessage(descrption);
updateMsgDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
updateMsgDialog.setButton(AlertDialog.BUTTON_POSITIVE, "安裝", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 啟動后台安裝服務 `SilentInstallService`
Intent intent = new Intent(UpdateActivity.this, SilentInstallService.class);
intent.putExtra("apkPath", apk);
context.startService(intent);
}
});
updateMsgDialog.setCanceledOnTouchOutside(false);
updateMsgDialog.show();
}
后台安裝服務
SilentInstallService.java
public class SilentInstallService extends IntentService {
static final String TAG = SilentInstallService.class.getSimpleName();
public SilentInstallService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
PackageManager pm = getPackageManager();
String apkPath = intent.getStringExtra("apkPath");
PackageInfo info = pm.getPackageArchiveInfo(apkPath,PackageManager.GET_ACTIVITIES);
if(install(apkPath) && info!=null){
startActivity(getPackageManager().getLaunchIntentForPackage(info.packageName));
}
}
public boolean install(String apkPath){
Process process = null;
BufferedReader errorStream = null;
try {
process = Runtime.getRuntime().exec("pm install -r "+apkPath+"\n");
process.waitFor();
errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String msg = "";
String line;
while((line=errorStream.readLine())!=null){
msg += line;
}
Log.i(TAG, "silent install msg : "+msg);
if(!msg.contains("Failure")){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(errorStream!=null){
try {
errorStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(process!=null){
process.destroy();
}
}
return false;
}
}
AndroidManifest.xml
注冊 StorageMountListener
和SilentInstallService
<receiver android:name=".StorageMountListener">
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file"/>
</intent-filter>
</receiver>
<service android:name="cn.rayland.update.SilentInstallService">
</service>
權限配置
到這個一切看起來盡善盡美了? but it does't work.
我們需要系統權限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
... ...
android:sharedUserId="android.uid.system">
但是我們會發現安裝失敗
error:Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES]
網上說這是因為你安裝了debug
權限簽名,再安裝系統sign
簽名就會有這個問題。需要卸載以前的app
再安裝。
然后我們又遇到了它
error:Failure [INSTALL_FAILED_SHARED_USER_INCOMPATIBLE]
這是因為我們在 AndroidManifest.xml
申明了系統簽名,然而並沒有。
我們需要一頓操作
-
找到編譯目標系統時的簽名證書platform.pk8和platform.x509.pem,在android源碼目錄build\target\product\security下
-
將簽名工具(signapk.jar)、簽名證書(platform.pk8和platform.x509.pem)及編譯出來的apk文件都放到同一目錄
然后命令行執行:
java -jar signapk.jar platform.x509.pem platform.pk8 input.apk output.apk
就能把路徑下的 input.apk
變成簽名的output.apk
當然你也可以使用現成的signapk,運行signApk.bat
就可以開心的更新了
其他方法
你也可以將更新APK服務SilentInstallService
編譯成一個app
燒在img
中。每次調用有系統簽名的SilentInstallService
app
即可。