Xamarin.Android实现App内版本更新

目录

  • 1、具体的效果
  • 2、代码实现
    • 2.1 基本原理
    • 2.2 开发环境
    • 2.3 具体代码
      • 2.3.1 基本设置
      • 2.3.2 系统的权限授予
      • 2.3.3 进度条的layout文件
      • 2.3.4 核心的升级文件
  • 3、代码下载
  • 4、知识点
  • 5、参考文献

1、具体的效果

有事需要在程序内集成自动更新的功能,网上找了下,改改适配下Xamarin.Android,效果如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、代码实现

2.1 基本原理

这个功能本质上,就是使用一个Intent打开一个apk文件进行预览。Android系统遇到预览apk文件时,就会弹出“是否进行安装更新”这类的安装框。

2.2 开发环境

VS2022,.NET7,Xamarin.Android、实体手机的Android版本:11

2.3 具体代码

2.3.1 基本设置

1、允许访问http
为了安全,从Android 7.0之后,不允许直接访问http的资源,因为我们会把安装包放在http的网络环境中,因此需要进行一个设置:在AndroidManifest.xmlapplication节点中,直接添加android:usesCleartextTraffic="true"即可。Android访问http的方案说明
2、设置FileProvider
同样,为了安全,在Android7.0之后,系统安装APP必须使用FileProvider,因此需要在AndroidManifest.xml中进行配置provider
3、权限设置
为了能够下载、存放、读取安装包,需要一系列的权限。需要在AndroidManifest.xml中进行配置

因此最终的配置文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.2" package="com.updateapp" android:installLocation="auto"><uses-sdk android:minSdkVersion="28" android:targetSdkVersion="33" /><!--为了能够安装apk文件,需要下面的一系列授权--><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /><application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">  <!--这句话是为了可以访问http的资源--><!--下面的配置,是为了设置FileProvider,其中用到了file_paths配置文件,具体如下--><provider android:name="androidx.core.content.FileProvider" android:authorities="com.updateapp.fileprovider" android:exported="false" android:grantUriPermissions="true"><meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data></provider></application>
</manifest>

Resources文件下创建xml文件夹,并创建file_paths.xml配置文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><!--安装包文件存储路径--><external-files-pathname="my_download"path="Download" /><external-pathname="."path="." />
</paths>

以上,就是第一步,程序的基本配置

2.3.2 系统的权限授予

除了AndroidManifest.xml中进行配置权限外,还需要进行权限的程序判定及授权

protected override void OnCreate(Bundle savedInstanceState)
{base.OnCreate(savedInstanceState);Xamarin.Essentials.Platform.Init(this, savedInstanceState);SetContentView(Resource.Layout.activity_main);Toolbar toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);SetSupportActionBar(toolbar);FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);fab.Click += FabOnClick;//版本跟踪,这个是和Android不一样的地方VersionTracking.Track();//初始化自动升级的功能autoUpdater=new AutoUpdater(this);try{//6.0之后才能使用动态授权if(Build.VERSION.SdkInt>=BuildVersionCodes.M){string[] permissions ={Manifest.Permission.ReadExternalStorage,Manifest.Permission.WriteExternalStorage,Manifest.Permission.AccessWifiState,Manifest.Permission.Internet};List<string> permissionList = new List<string>();for (int i = 0; i < permissions.Length; i++){if(ActivityCompat.CheckSelfPermission(this, permissions[i])!=Permission.Granted){ permissionList.Add(permissions[i]);}}//if(permissionList.Count==0){//更新程序autoUpdater.checkUpdate();}else{//获取授权ActivityCompat.RequestPermissions(this, permissions, 100);}}}catch(Exception e){Toast.MakeText(this,"发生异常:"+e.Message,ToastLength.Long).Show();}
}public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);bool checkPermissionFlag = true;if (requestCode == 100){for(int i = 0; i < permissions.Length; i++){if (grantResults[i]== Permission.Granted){checkPermissionFlag = checkPermissionFlag && true;}else{checkPermissionFlag = checkPermissionFlag && false;}}if(!checkPermissionFlag){//授权程序Snackbar.Make(View.Inflate(this,Resource.Id.activity_main_layout,null),"需要授权",Snackbar.LengthIndefinite).SetAction("ok",new Action<View>(delegate (View obj){ActivityCompat.RequestPermissions(this, permissions, 100);})).Show();}else{//更新程序Toast.MakeText(this, "授权后,可以进行更新程序啦!", ToastLength.Long).Show();autoUpdater.checkUpdate();}}base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}

2.3.3 进度条的layout文件

在layout文件夹中添加progress.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:id="@+id/titleBar"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><TextViewandroid:id="@+id/txtStatus"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="状态"android:textSize="10sp"android:textStyle="normal" /><ProgressBarandroid:id="@+id/progress"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_toLeftOf="@id/txtStatus" /></LinearLayout>
</LinearLayout>

2.3.4 核心的升级文件

using Android.App;
using Android.Content;
using Android.Net;
using Android.OS;
using Android.Runtime;
using Android.Systems;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.IO;
using Java.Net;
using Java.Util.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Context = Android.Content.Context;
using Environment = Android.OS.Environment;namespace UpdateApp
{public class AutoUpdater{private Android.App.AlertDialog confirmDialog = null; //确认是否下载的对话框private Android.App.AlertDialog loadingDialog = null; //正在下载的对话框public MainActivity mainActivity;private UpdateHandler updateHandler;// 保存APK的文件名private static string saveFileName = "my.apk";private static File apkFile;// 进度条与通知UI刷新的handler和msg常量public ProgressBar mProgress;public TextView txtStatus;public int progress;// 当前进度public AutoUpdater(MainActivity activity) {mainActivity = activity;updateHandler = new UpdateHandler(this);apkFile =new File(mainActivity.GetExternalFilesDir(Environment.DirectoryDownloads), saveFileName);}//主方法public void checkUpdate(){//新开启一个线程,进行下载及逻辑判断Task.Run(() => {//获取本地的版本名称(一般而言就是1.0、1.1、1.2的纯数字)string localVersionName = VersionTracking.CurrentVersion;//获取服务器的版本string remoteServerVersion = "2.2"; //远程获取服务器上最新版本,这儿省事儿了,直接默认取了一个较大的值if (Convert.ToDouble(localVersionName)< Convert.ToDouble(remoteServerVersion)){//启动升级的界面updateHandler.SendEmptyMessage((int)UpdateStatusEnum.BeginLoad);}});}//弹框进行下载public void ShowUpdateDialog(){Android.App.AlertDialog.Builder builder = null;builder = new Android.App.AlertDialog.Builder(mainActivity);confirmDialog = builder.SetTitle("软件版本更新").SetMessage("有最新的软件包,请下载并安装!").SetPositiveButton("立即下载", (s, e) => { //确定按钮及内部方法ShowDownloadDialog();confirmDialog.Dismiss();}).SetNegativeButton("以后再说", (s, e) => { //界面上的关闭按钮及方法confirmDialog.Dismiss();}).Create();confirmDialog.Show();}//弹出确认下载的进度条的内容private void ShowDownloadDialog(){CancellationTokenSource cts = new CancellationTokenSource();CancellationToken cancellationToken = cts.Token;Android.App.AlertDialog.Builder builder = null;builder = new Android.App.AlertDialog.Builder(mainActivity);View view = mainActivity.LayoutInflater.Inflate(Resource.Layout.progress, null, false);mProgress = view.FindViewById<ProgressBar>(Resource.Id.progress);txtStatus = view.FindViewById<TextView>(Resource.Id.txtStatus);loadingDialog = builder.SetView(view).SetTitle("正在更新").SetNegativeButton("取消下载", (s, e) => { //界面上的关闭按钮及方法cts.Cancel();}).Create();loadingDialog.Show();DownloadApk(cancellationToken);}//下载APPprivate void DownloadApk(CancellationToken cancellationToken){Task.Run(() => {try { URL url = new URL(@"http://xxx/xxx/xxx/com.updateapp.apk");//apk的网络地址URLConnection conn = url.OpenConnection();conn.Connect();int length = conn.ContentLength;System.IO.Stream ins = conn.InputStream;FileOutputStream fos = new FileOutputStream(apkFile);int count = 0;byte[] buf = new byte[1024];while (!cancellationToken.IsCancellationRequested){int numread = ins.Read(buf);count += numread;progress = (int)(((float)count / length) * 100);//下载进度Message message = new Message();message.What = (int)UpdateStatusEnum.Loading;Bundle extras = new Bundle();extras.PutInt("progress", progress);message.Data = extras;updateHandler.SendMessage(message);if (numread <= 0){Message msg = new Message();//下载完成msg.What = (int)UpdateStatusEnum.Finish; extras.PutInt("progress", 100);msg.Data = extras;updateHandler.SendMessage(msg);//关闭下载框if(loadingDialog!=null) loadingDialog.Dismiss();    break;}fos.Write(buf, 0, numread);}fos.Close();ins.Close();}catch (System.OperationCanceledException el){Log.Info("info", "用户取消了操作!"+el.Message);}catch (AggregateException e){foreach (Exception ex in e.InnerExceptions){Log.Info("info", "发生异常!" + ex.Message);}}}, cancellationToken);}public void installAPK(){try{if(!apkFile.Exists()){Toast.MakeText(mainActivity, "下载的文件不存在!", ToastLength.Short).Show();return;}//这儿是整个的核心Intent intent = new Intent();intent.SetAction(Intent.ActionView);intent.AddFlags(ActivityFlags.NewTask);intent.AddFlags(ActivityFlags.GrantReadUriPermission);intent.AddFlags(ActivityFlags.GrantWriteUriPermission);if (Build.VERSION.SdkInt >=Android.OS.BuildVersionCodes.N){string packageName = mainActivity.ApplicationContext.PackageName;string authority = new StringBuilder(packageName).Append(".fileprovider").ToString();Android.Net.Uri apkUri = FileProvider.GetUriForFile(mainActivity, authority, apkFile);intent.SetDataAndType(apkUri, "application/vnd.android.package-archive");}else{intent.SetDataAndType(Android.Net.Uri.FromFile(apkFile), "application/vnd.android.package-archive");}mainActivity.StartActivity(intent);}catch (Exception ex){Toast.MakeText(mainActivity, "安装installAPK发生异常"+ex.Message, ToastLength.Short).Show();}}}//状态枚举 public enum UpdateStatusEnum:int{ BeginLoad=1,Loading=2,Finish=3}//Handler事件public class UpdateHandler : Android.OS.Handler{private WeakReference<AutoUpdater> weakReference;[Obsolete]public UpdateHandler(AutoUpdater autoUpdater){weakReference = new WeakReference<AutoUpdater>(autoUpdater);}public override void HandleMessage(Message msg){AutoUpdater targetActivity;bool isGetSuccess = weakReference.TryGetTarget(out targetActivity);if (isGetSuccess){switch (msg.What){case (int)UpdateStatusEnum.BeginLoad:targetActivity.ShowUpdateDialog();break;case (int)UpdateStatusEnum.Loading://获取状态数据,并进行展示int progress = msg.Data.GetInt("progress");targetActivity.txtStatus.SetText(progress + "%",TextView.BufferType.Normal);targetActivity.mProgress.SetProgress(progress, true);break;case (int)UpdateStatusEnum.Finish:Toast.MakeText(targetActivity.mainActivity, "下载完毕", ToastLength.Long).Show();targetActivity.installAPK();break;default:break;}}base.HandleMessage(msg);}}}

上面是这个的核心

3、代码下载

代码下载

4、知识点

1、Handler的用法,C#与Java还是不同的,这里涉及到的知识点是匿名类和委托。C#的匿名类是一个field的集合,不能包含方法
2、Android中更新应用的逻辑

每个 Android 应用均有一个唯一的应用 ID,像 Java 或 Kotlin 软件包名称一样,例如 com.example.myapp。此 ID 可以作为每个应用在设备上的唯一标识。Android 设备一次只能安装一个具有指定应用 ID 的应用。
为了让 Android 平台接受更新,更新必须满足以下条件:
应用更新的应用 ID 必须与已安装应用的应用 ID 相同。
应用更新的签名证书必须与已安装应用的签名证书相同,或者必须包含有效的 proof-of-rotation。
应用更新的版本代码必须高于或等于已安装应用的版本代码。
在某些情况下,用户可能需要接受更新。
请注意,如果多个更新具有相同的签名证书并且具有相同或更高的版本代码,Android 内部并没有防范措施能够阻止不同的安装程序更新应用。
如要安装不符合上述条件的应用,用户必须先卸载当前已安装的版本,而卸载操作会清除设备上的所有应用数据。

5、参考文献

主要参考了前两个
1、Android App自动安装
2、Android APP 自动更新实现(适用Android9.0)
3、【Android】APP检测版本升级更新、apk安装
4、Andrioid FileProvider在Xamarin.Forms中的使用
5、Xamarin.Android 中 Handler 的使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/139148.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业级数据仓库-理论知识

D3 AM 大数据中间件 Hive&#xff1a;将SQL转化成分布式Map/Reduce进行运算&#xff0c;也支持转换成Spark,需要单独安装Hive集群才能访问Spark,支持60%的SQL&#xff0c;延迟比较大。SparkSQL:属于Spark生态圈&#xff0c;Hive on Sqark。HBase: NoSQL,高并发读&#xff0c;适…

thinkphp8路由

thinkphp8已出来有好一段时间了。这些天闲来无事&#xff0c;研究了下tp8的路由。默认情况下&#xff0c;tp8的路由是在route\app.php的文件里。但在实际工作中&#xff0c;我们并不会这样子去写路由。因为这样不好管理。更多的&#xff0c;是通过应用级别去管理路由。假如项目…

设计模式之解释器模式

一、定义 1、定义 Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.&#xff08;给定一门语言&#xff0c;定义它的语法的一种表示&#xff0c;并定义一个解释器&…

Winform直接与Wpf交互

Winform项目中&#xff0c;可以直接使用wpf中的自定义控件和窗体 测试环境&#xff1a; vistual studio 2017 window 10 一 winform直接使用wpf的自定义控件 步骤如下&#xff1a; 1 新建winfrom项目&#xff0c;名为WinFormDemo&#xff0c;默认有一个名为Form1的窗体…

【GAMES103】基于物理的计算机动画入门(1)前置的基础数学知识

GAMES103: 基于物理的计算机动画入门 链接&#xff1a;GAMES103 1. 坐标系的划分 在游戏引擎中分为右手和左手坐标系&#xff0c;区分的依据是什么&#xff1f; 上图可以看到如果是左手坐标系&#xff0c;那么所有的物体都在屏幕后面&#xff0c;意味着x&#xff0c;y&#x…

物联网的未来:连接的智能世界

物联网&#xff08;IoT&#xff09;是引领我们走向未来的一项关键技术。它让物品通过互联网进行连接&#xff0c;交流&#xff0c;开创了智能生活新时代。预计到2025年&#xff0c;全球将拥有超过410亿的IoT设备。在对人类生活的每个方面产生影响的同时&#xff0c;物联网也正在…

2023华为杯研究生数学建模竞赛CDEF题思路+模型代码

全程更新华为杯研赛CDEF题思路模型及代码&#xff0c;大家查看文末名片获取 华为杯C题思路分析 问题一 在每个评审阶段&#xff0c;作品通常都是随机分发的&#xff0c;每份作品需要多位评委独立评审。为了增加不同评审专家所给成绩之间的可比性&#xff0c;不同专家评审的作…

【kafka实战】03 SpringBoot使用kafka生产者和消费者示例

本节主要介绍用SpringBoot进行开发时&#xff0c;使用kafka进行生产和消费 一、引入依赖 <dependencies><dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId></dependency><depen…

zabbix自定义监控、钉钉、邮箱报警

目录 一、实验准备 二、安装 三、添加监控对象 四、添加自定义监控项 五、监控mariadb 1、添加模版查看要求 2、安装mariadb、创建用户 3、创建用户文件 4、修改监控模版 5、在上述文件中配置路径 6、重启zabbix-agent验证 六、监控NGINX 1、安装NGINX&#xff0c…

207.Flink(二):架构及核心概念,flink从各种数据源读取数据,各种算子转化数据,将数据推送到各数据源

一、Flink架构及核心概念 1.系统架构 JobMaster是JobManager中最核心的组件,负责处理单独的作业(Job)。一个job对应一个jobManager 2.并行度 (1)并行度(Parallelism)概念 一个特定算子的子任务(subtask)的个数被称之为其并行度(parallelism)。这样,包含并行子任…

实验五 熟悉 Hive 的基本操作

实验环境&#xff1a; 1.操作系统&#xff1a;CentOS 7。 2.Hadoop 版本&#xff1a;3.3.0。 3.Hive 版本&#xff1a;3.1.2。 4.JDK 版本&#xff1a;1.8。 实验内容与完成情况&#xff1a; &#xff08;1&#xff09;创建一个内部表 stocks&#xff0c;字段分隔符为英文逗号…

爬虫 — Scrapy 框架(一)

目录 一、介绍1、同步与异步2、阻塞与非阻塞 二、工作流程三、项目结构1、安装2、项目文件夹2.1、方式一2.2、方式二 3、创建项目4、项目文件组成4.1、piders/__ init __.py4.2、spiders/demo.py4.3、__ init __.py4.4、items.py4.5、middlewares.py4.6、pipelines.py4.7、sett…

BOM与DOM--记录

BOM基础&#xff08;BOM简介、常见事件、定时器、this指向&#xff09; BOM和DOM的区别和联系 JavaScript的DOM与BOM的区别与用法详解 DOM和BOM是什么&#xff1f;有什么作用&#xff1f; 图解BOM与DOM的区别与联系 BOM和DOM详解 JavaScript 中的 BOM&#xff08;浏览器对…

睿趣科技:抖音开通蓝V怎么操作的

在抖音这个充满创意和活力的社交媒体平台上&#xff0c;蓝V认证成为了许多用户的梦想之一。蓝V认证不仅是身份的象征&#xff0c;还可以增加用户的影响力和可信度。但是&#xff0c;要在抖音上获得蓝V认证并不是一件容易的事情。下面&#xff0c;我们将介绍一些操作步骤&#x…

小米笔试题——01背包问题变种

这段代码的主要思路是使用动态规划来构建一个二维数组 dp&#xff0c;其中 dp[i][j] 表示前 i 个产品是否可以组合出金额 j。通过遍历产品列表和可能的目标金额&#xff0c;不断更新 dp 数组中的值&#xff0c;最终返回 dp[N][M] 来判断是否可以组合出目标金额 M。如果 dp[N][M…

Android studio安卓生成APK文件安装包方法

1.点击Build->Generate Signed Bundle/APK 2.选择APK 3.首次生成&#xff0c;没有jks文件&#xff0c;就点击Create new。再次生成&#xff0c;直接点Next 4.选择创建jks文件路径 5.点击Next 6.选择release 7.生成完成的apk安装包路径

【论文阅读 08】Adaptive Anomaly Detection within Near-regular Milling Textures

2013年&#xff0c;太老了&#xff0c;先不看 比较老的一篇论文&#xff0c;近规则铣削纹理中的自适应异常检测 1 Abstract 在钢质量控制中的应用&#xff0c;我们提出了图像处理算法&#xff0c;用于无监督地检测隐藏在全局铣削模式内的异常。因此&#xff0c;我们考虑了基于…

uniapp小程序点击按钮直接退出小程序效果demo(整理)

点击按钮直接退出小程序 <navigator target"miniProgram" open-type"exit">退出小程序</navigator>

Android Key/Trust Store研究+ssl证书密钥

前言&#xff1a;软件搞环境涉及到了中间件thal trustzone certificate key&#xff0c;翻译过来是thal信任区域证书密钥 &#xff0c;不明白这是什么&#xff0c;学习一下 ssl证书密钥 SSL密钥是SSL加密通信中的重要组成部分。SSL证书通过加密算法生成&#xff0c;用于保护网…

Oracle 11g RAC部署笔记

搭了三次才搭好&#xff0c;要记录一下。 1. Oracle 11g RAC部署的相关步骤以及需要的包&#xff0c;可以参考这里。 Oracle 11g RAC部署_12006142的技术博客_51CTO博客Oracle 11g RAC部署&#xff0c;Oracle11gRAC部署操作环境&#xff1a;CentOS7.4Oracle11.2.0.4一、主机网…