IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android9 ab系统OTA升级总结 -> 正文阅读

[移动开发]Android9 ab系统OTA升级总结

Android9 ab系统OTA升级总结

OTA升级介绍 官方介绍 https://source.android.google.cn/devices/tech/ota/tools#multiple-skus

1.OTA升级包的制作

OTA升级有两种方式,全包升级和差分升级总体升级操作步骤类似

首先需要对代码做一些改动:如下在/build/core/Makefile中添加如下代码:

/build/core/Makefile
2850 	$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
2851 	    build/make/tools/releasetools/add_img_to_target_files -a -v -p $(HOST_OUT) $(zip_root)
2852 	@# Zip everything up, preserving symlinks and placing META/ files first to
2853 	@# help early validation of the .zip file while uploading it.
2854 	$(hide) find $(zip_root)/META | sort >$@.list
2855 	$(hide) find $(zip_root) -path $(zip_root)/META -prune -o -print | sort >>$@.list
2856 	$(hide) $(SOONG_ZIP) -d -o $@ -C $(zip_root) -l $@.list
++++    $(hide) ./build/tools/releasetools/replace_img_from_target_files.py $@ $(PRODUCT_OUT)
2858 .PHONY: target-files-package
2859 target-files-package: $(BUILT_TARGET_FILES_PACKAGE)

因为replace_img_from_target_files.py在源码中不存在,所以需要在build/tools/releasetools/下新建replace_img_from_target_files.py文件内容如下:

#!/usr/bin/env python
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
 
"""
Given a target-files zipfile that does contain images (ie, does
have an IMAGES/ top-level subdirectory), replace the images to
the output dir.
Usage:  replace_img_from_target_files target_files output
"""
 
import sys
 
if sys.hexversion < 0x02070000:
  print >> sys.stderr, "Python 2.7 or newer is required."
  sys.exit(1)
 
import errno
import os
import re
import shutil
import subprocess
import tempfile
import zipfile
 
image_replace_list = ["boot.img","system.img"]
 
# missing in Python 2.4 and before
if not hasattr(os, "SEEK_SET"):
  os.SEEK_SET = 0
 
def main(argv):
 
  if len(argv) != 2:
    sys.exit(1)
 
  if not os.path.exists(argv[0]):
    print "Target file:%s is invalid" % argv[0]
    sys.exit(1)
 
  if not os.path.exists(argv[1]):
    print "Output dir:%s is invalid" % argv[1]
    sys.exit(1)
 
  zf = zipfile.ZipFile(argv[0], 'r')
 
  for img in zf.namelist():
    if img.find("IMAGES/") != -1:
      if img.find(".img") != -1:
        data = zf.read(img)
        name = img.replace("IMAGES/", '')
        if name in image_replace_list:
          print "Replace %s" % name
          name = '/'.join((argv[1], name))
          file = open(name, "w")
          file.write(data)
          file.close()
 
if __name__ == '__main__':
  main(sys.argv[1:])

至于为什么要加上上面的代码,我们后面会去介绍

参考 Android OTA差分包升级失败 img sha 验证问题

(1)编译基础版本的系统 在代码库的根目录执行如下指令

source build/envsetup.sh && make dist DIST_DIR=dist_output_v1

上述命令执行成功后会在dist_output_v1 生成如下文件:

其中xxx-ota-eng.yuwei.zip是这个初始版本的全包升级包,你可以用这个包将其他版本升级到此版本.

xxx-target_files-eng.yuwei.zip是生成OTA升级包的资源包。我们生成差分包也是基于这个资源包生成的。

同时请将out/target目录下的系统镜像保存下来,这个是基础版本的镜像例如 boot.img mdtp.img vendor.img system.img persist.img userdata.img

(2)修改系统比如预置一个系统app 接着按照上面的方式生成第二个版本的资源包

make dist DIST_DIR=dist_output_v2

这时你可以在dist_output_v2中看到上面一样的文件。此时,如果你需要全包升级那么需要导出dist_output_v2/xxx-ota-eng.yuwei.zip 即可进行升级,但是如果需要差分包升级那么还需要制作差分包见(3),不需要差分包升级的可以跳过第三步

(3)制作差分包 在代码根目录下执行如下指令即可制作差分包

? ./build/tools/releasetools/ota_from_target_files -v -i dist_output_v1/xxx-target_files-eng.yuwei.zip dist_output_v2/xxx-target_files-eng.yuwei.zip OTA/v1_v2_update.zip

上面这个命令使用的是./build/tools/releasetools/ota_from_target_files这个文件去生成差分包的,-v会把生成差分包过程中的log打印到shell上,-i (–incrementral_from)则是生成差分包的必要选项.总结下来格式就是

ota_from_target_files -v -i 原始版本的target_files.zip 新版本的target_files.zip 要生成的差分包的update.zip

2.制作升级demo app

ab系统的升级需要调用UpdateEngine的applyPayload函数,而这个api是系统api,所以还需要系统权限。

准备工作:

1.系统签名

要想使用Android Studio来调试apk,那么需要将系统签名导入到Android Studio中,步骤如下:

(1) 在代码库根目录新建文件夹 mkdir key

(2) 将系统签名拷贝过去 系统签名一般存放在/build/target/product/security/ 将其中的platform.pk8和platform.x509.pem拷贝到之前创建的目录下

(3)将系统签名的key转换成Android Studio中的keystore;

在拷贝了系统签名的目录下执行如下指令

openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt
openssl pkcs12 -export -in platform.x509.pem -inkey     shared.priv.pem -out shared.pk12 -name androiddebugkey
密码都是:android
keytool -importkeystore -deststorepass android -destkeypass android -destkeystore platform.keystore -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias androiddebugkey

这时会生成一个platform.keystore,把它拷贝到windows系统中

接着时配置AS的环境

(4)在模块级别的build.gradle中添加如下代码

android {
++	signingConfigs {
++    	platform {
++        	storeFile file('D:\\UbuntuShared\\platform.keystore')  //这里填写你存放platform.keystore的路径
++        	storePassword 'android'
++        	keyAlias 'androiddebugkey'
++        	keyPassword 'android'
++    	}
++	}
    compileSdk 31

    defaultConfig {
        applicationId "com.example.otaupgradedemo"
        minSdk 28
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        signingConfig signingConfigs.platform
    }
++  buildTypes {
++      release {
++          minifyEnabled false
++          proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
++      }
++      debug {
++          signingConfig signingConfigs.platform
++      }
++  }
        

此时你就可以直接将你的apk当作系统apk来调试

参考 Apk 使用系统签名

2.系统接口库

由于demo apk需要使用到系统的接口,但是sdk是不提供这些接口,所以需要将系统的framework.jar导入到Android Studio中

(1)将代码编译的framework.jar拷贝到windows系统,framework.jar的路径如下:

out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar

接着在AndroidStudio中将app切换成project模式,之后将上面的class.jar拷贝到项目的libs目录接着右键点击选择 add as library即可将库导入你的app中

参考 Android 引入framework.jar
Demo 源代码如下:

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.otaupgradedemo"
    android:sharedUserId="android.uid.system">
    <!--由于是系统的api,所以需要加上系统的share uid,并且需要系统签名-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.REBOOT" />
    <uses-permission android:name="android.permission.RECOVERY" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/upgrade"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="升级"
        android:onClick="update"
        android:enabled="true" />
    <Button
        android:id="@+id/verify"
        android:onClick="verify"
        android:text="绑定UpdateEngine服务"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:onClick="prase"
        android:text="解析zip文件"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity.java

package com.example.otaupgradedemo;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.PowerManager;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;

import android.os.Build;
import android.os.Bundle;
import android.os.Environment;

import android.os.RecoverySystem;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.security.GeneralSecurityException;
import java.text.DecimalFormat;

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "YW_OTA";
    private PowerManager powerManager;
    UpdateEngine mUpdateEngine = new UpdateEngine();
    //升级包的路径
    public String OTA_PACKAGE = "/data/ota_package"+File.separator+"update.zip";
    UpdateEngineCallback mUpdateEngineCallback = new UpdateEngineCallback() {
        @Override
        public void onStatusUpdate(int status, float percent) {

            Log.d(TAG, "onStatusUpdate  status: " + status);

            switch (status) {
                case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT:
                    progressDialog.dismiss();
                    rebootNow();
                    break;
                case UpdateEngine.UpdateStatusConstants.DOWNLOADING:// 回调状态,升级进度
                    progressDialog.setProgress((int) (percent * 100));
                    DecimalFormat df = new DecimalFormat("#");
                    String progress = df.format(percent * 100);

                    Log.d(TAG, "update progress: " + progress);

                    break;
                default:
                    // noop
            }

        }

        @Override
        public void onPayloadApplicationComplete(int errorCode) {
            progressDialog.cancel();
            Log.d(TAG, "onPayloadApplicationComplete errorCode=" + errorCode);

            if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) {// 回调状态
                Log.d(TAG, "UPDATE SUCCESS!");
            }
        }
    };

    File upgradePackage;
    UpdateParser.ParsedUpdate parsedUpdate;
    ProgressDialog progressDialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String packagePath =OTA_PACKAGE;
        Log.d("YW","patch = "+packagePath);

        upgradePackage = new File(packagePath);
        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

    }

    public void update(View view) {
        if(parsedUpdate == null){
            Toast.makeText(this, "please click prase button first", Toast.LENGTH_SHORT).show();
            return;
        }
        progressDialog = ProgressDialog.show(this,"update system","updating ...");
        progressDialog.setMax(100);
        mUpdateEngine.applyPayload(
                parsedUpdate.mUrl, parsedUpdate.mOffset, parsedUpdate.mSize, parsedUpdate.mProps);
    }

    public void verify(View view) {
        boolean success = mUpdateEngine.bind(mUpdateEngineCallback);
        Toast.makeText(this, "bindService "+(success?" success !!":"fail !!"), Toast.LENGTH_SHORT).show();
    }
    /**
     * Reboot the system.
     */
    private void rebootNow() {
        if(upgradePackage.exists()){
            upgradePackage.delete();
        }
        Log.e("YW", "rebootNow");
        powerManager.reboot("systemUpdate");
    }

    public void prase(View view) {
        if(!upgradePackage.exists()){
            Log.d("YW","No such file or dir "+upgradePackage.getAbsolutePath());
            Toast.makeText(this, "No such file or dir "+upgradePackage.getAbsolutePath(), Toast.LENGTH_SHORT).show();
            return;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    parsedUpdate = UpdateParser.parse(upgradePackage);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

解析升级包的内容类UpdateParser.java 参考 /packages/apps/Car/SystemUpdater/src/com/android/car/systemupdater/UpdateParser.java

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.otaupgradedemo;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/** Parse an A/B update zip file. */
class UpdateParser {

    private static final String TAG = "UpdateLayoutFragment";
    private static final String PAYLOAD_BIN_FILE = "payload.bin";
    private static final String PAYLOAD_PROPERTIES = "payload_properties.txt";
    private static final String FILE_URL_PREFIX = "file://";
    private static final int ZIP_FILE_HEADER = 30;

    private UpdateParser() {
    }

    /**
     * Parse a zip file containing a system update and return a non null ParsedUpdate.
     */

    static ParsedUpdate parse(File file) throws IOException {

        long payloadOffset = 0;
        long payloadSize = 0;
        boolean payloadFound = false;
        String[] props = null;

        try (ZipFile zipFile = new ZipFile(file)) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                long fileSize = entry.getCompressedSize();
                if (!payloadFound) {
                    payloadOffset += ZIP_FILE_HEADER + entry.getName().length();
                    if (entry.getExtra() != null) {
                        payloadOffset += entry.getExtra().length;
                    }
                }

                if (entry.isDirectory()) {
                    continue;
                } else if (entry.getName().equals(PAYLOAD_BIN_FILE)) {
                    payloadSize = fileSize;
                    payloadFound = true;
                } else if (entry.getName().equals(PAYLOAD_PROPERTIES)) {
                    try (BufferedReader buffer = new BufferedReader(
                            new InputStreamReader(zipFile.getInputStream(entry)))) {
                        props = buffer.lines().toArray(String[]::new);
                    }
                }
                if (!payloadFound) {
                    payloadOffset += fileSize;
                }

                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, String.format("Entry %s", entry.getName()));
                }
            }
        }
        return new ParsedUpdate(file, payloadOffset, payloadSize, props);
    }

    /** Information parsed from an update file. */
    static class ParsedUpdate {
        final String mUrl;
        final long mOffset;
        final long mSize;
        final String[] mProps;

        ParsedUpdate(File file, long offset, long size, String[] props) {
            mUrl = FILE_URL_PREFIX + file.getAbsolutePath();
            mOffset = offset;
            mSize = size;
            mProps = props;
        }

        /** Verify the update information is correct. */
        boolean isValid() {
            return mOffset >= 0 && mSize > 0 && mProps != null;
        }

        @Override
        public String toString() {
            return String.format(Locale.getDefault(),
                    "ParsedUpdate: URL=%s, offset=%d, size=%s, props=%s",
                    mUrl, mOffset, mSize, Arrays.toString(mProps));
        }
    }
}

apk的升级demo也可以参考Google的官方demo:https://cs.android.com/android/platform/superproject/+/master:bootable/recovery/updater_sample/

上面的链接需要VPN才能访问

3.升级验证:

1.先将设备烧入基础版本(没有预置三方apk的版本)

2.将升级包push 到data/ota_package目录下

adb root && adb push update.zip data/ota_package

3.关闭系统的selinux权限(在正式版本中需要解决selinux的权限问题)

adb root && adb shell setenforce 0

4.启动demo app,界面如下
在这里插入图片描述

先点击解析zip文件,接着点击绑定服务,最后点击升级会出现一个弹框,等待重启,即可升级成功

4.遇到的问题

1.修改了系统app(例如:settings)的app的名字发现升级前后没有改变

将apk反编译后会发现改动是生效的,但是由于launcher的缓存导致升级后改动没有生效,需要将/data/data/com.android.launcher3/databases数据库删除后重启即可看到相应改动

2.在升级的过程中报错

The hash of the source data on disk for this operation doesn’t match the expected value. This could mean that the delta update payload was targeted for another version, or that the source partition was modified after it was installed, for example, by mounting a filesystem.

参考https://blog.csdn.net/qq_27061049/article/details/107388136

大概意思是make dist也会重新打包image导致生成差分包时的源文件和PRODUCT_OUT目录下的image hash值发生了差别继而导致hash值的验证失败

解决办法:本文开头需要添加的代码就是这个问题的解决方法,思路是在生成target_files.zip包时将其中的image替换成PRODUCT_OUT目录下的image。

3.编译过程中的缓存

在编译过程中一定要保证你的前后两个版本时有差异的,必要情况下可以先执行make clean清除out目录下的所有文件再编译新的版本

4.权限问题,最终确定将ota的升级包统一拷贝到data/ota_package目录下最后再交给update_engine去升级这就需要升级应用具有data/ota_package的读写权限

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-11 18:56:29  更:2021-09-11 18:57:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:07:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码