Android App內檢測更新新版本APK
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
即可。
Android App內檢測更新新版本APK