1. 程式人生 > >Android App內檢測更新新版本APK

Android App內檢測更新新版本APK

調用 led com nta contex context smis 手動 方法

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註冊 StorageMountListenerSilentInstallService


<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