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注册 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即可。

原文地址:https://www.cnblogs.com/chenjy1225/p/9662494.html

时间: 2024-11-06 18:33:08

Android App内检测更新新版本APK的相关文章

Android app内语言环境切换

逻辑很简单: 1  app内所有activity继承自BaseActivity或BaseActivity派生出来的子类,BaseActivity中维护了一个静态的app Activity访问栈,在创建和销毁时会执行压栈和出栈操作,所以mLocalStack内维持的是app中正在运行的activity. 2  将app的语言环境存储在SharedPreferences中,避免app重启时修改状态不改变:在BaseActivity创建时取出语言环境字符串并初始化Activity语言环境(initLa

Android App内置键盘开发

参考: http://blog.csdn.net/hfsu0419/article/details/7924673 布局文件activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match

Android App退出检测

app的退出检测是很难的,但是获取app“要退出”的状态就容易多了,退出的瞬间并不是真的退出了,ActivityManager要销毁activity,也需要一些时间和资源的. 先见下面的运行效果:  gif做的比价粗啊, 两个activity的界面就不介绍了,主要是在APP启动的时候开启一个服务,application代码如下: public class MyApplication extends Application { @Override public void onCreate() {

ios 在APP内提示更新

http://www.jianshu.com/p/24daf5147bda     ios如何在应用内部提示更新  两颗星 http://www.jianshu.com/p/2ba10a58bb02     iOS自定义版本更新检查 三颗星

egret 打包android app 时 js 错误

创建android app,  编译后生成apk,手机安装后无法运行,弹出以下错误: JS error report error  assets/egret-game/libs/core/egret/context/devices/nativedevicecontext.js:203:egret_native.isFileExists is  not a function 点击错误提示框上 ok , 黑屏 出现以上错误时, 可以检查一下, core版本是否与android support 版本一

android产品研发(十四)--&gt;App升级与更新

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android app中的轮训操作,讲解的内容主要包括:我们在App中使用轮训操作的情景,作用以及实现方式等.一般而言我们使用轮训操作都是通过定时任务的形式请求服务器并更新用户界面,轮训操作都有一定的使用生命周期,即在一定的页面中启动轮操作,然后在特定的情况下关闭轮训操作,这点需要我们尤为注意,我们还介绍了使用Timer和Handler实现轮训操作的实例,更多关于App中轮训操作的信息,可参考我的:android产品研发(十三)–>App轮训

Android第五期 - 更新自己的apk本地与网络两种方法

首先是本地: ParseXmlService部分: package com.szy.update; import java.io.InputStream; import java.util.HashMap; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element

Android实现App版本自动更新

现在很多的App中都会有一个检查版本的功能.例如斗鱼TV App的设置界面下: 当我们点击检查更新的时候,就会向服务器发起版本检测的请求.一般的处理方式是:服务器返回的App版本与当前手机安装的版本号进行对比. (1)如果服务器所返回的版本号大于当前App版本号那么此时手机所安装的App不是最新版.可以提示用户升级. (2)如果不大于当前版本号,可以提示用户为最新版本: 版本升级,也分为两种处理方式: (1)跳转到App某市场(例如:360手机助手),然后根据包名在市场定位到该App,通过市场下

Android App补丁更新

上一周比较忙,忙的不可开交,写的文章也就两篇,在此希望大家见谅.这周呢,突然闲下来了,有时间了,就重构了下代码,捣鼓点前卫的技术,沉淀沉淀.所以呢,今天就分享下这几天研究的东西. 移动互联网主打的就是用户体验和产品的快速迭代,通过用户反馈和用户行为跟踪及时调整产品方向,这样才能持续保持生命力和创造力.说的接地气点就是,你频繁的升级更新,有时只是修复了几个bug或者微调了下界面,就让用户下载10几兆甚至更大的apk,而且在目前国内这个4G还不是普及的时候,对用户来说是很不友好的.有没有这样一种策略