终于把下载安装更新的功能整出来了,记录关键点

news2024/11/25 10:01:32

我的第一个安卓应用终于也有了APP内安装更新的功能(赶上末班车了吗),记录一些关键点,方方面面的。

托管检测更新和下载服务

由于没有服务器,这两个核心功能可以托管到一些比较好的平台。检测我用的是蒲公英分发(内测阶段),下载用的则是无限蓝云(hhh)。如果蒲公英过审了也可以只用一个,不知道难度大不大……

安装apk

高版本需要fileprovider,其实不用的话直接vmpolicy微调一下也行。

两个关键点都需要在清单文件中处理:1、定义 fileprovider,2:声明权限(否则没有反应)。

manifest:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

<application ...>

	<provider
		android:name="androidx.core.content.FileProvider"
		android:authorities="${applicationId}.fileprovider"
		android:grantUriPermissions="true"
		android:exported="false">
		<meta-data
			android:name="android.support.FILE_PROVIDER_PATHS"
			android:resource="@xml/file_paths"
			/>
	</provider>
</application>

其中 android:resource="@xml/file_paths"需要提供一个清单文件res/xml/file_paths.xml,可如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-cache-path path="apks/" name="apks"/>
</paths>

这样,需要下载安装包到: 外部储存->临时文件夹( /storage/0/Android/data/包名/cache )中的 apks 文件夹: File target = new File(getExternalCacheDir(), "apks/"+versionName); ,才能被 FileProvider 识别。

Java 调用 :

	private void startUpdateInstall(File target) {
		try {
			Intent intent = new Intent(Intent.ACTION_VIEW);
			File file = target;
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
				Uri apkUri = FileProvider.getUriForFile(PDICMainActivity.this, "包名.fileprovider", file);
				intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
				intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
			} else {
				intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				Uri uri = Uri.fromFile(file);
				intent.setDataAndType(uri, "application/vnd.android.package-archive");
			}
			startActivity(intent);
		} catch (Exception e) {
			CMN.debug(e);
		}
	}

FileProvider还可以包含更多文件夹, 参考 Stack Overflow - ‘Failed to find configured root’:

  • <files-path/> --> Context.getFilesDir()
  • <cache-path/> --> Context.getCacheDir()
  • <external-path/> --> Environment.getExternalStorageDirectory()
  • <external-files-path/> --> Context.getExternalFilesDir(String)
  • <external-cache-path/> --> Context.getExternalCacheDir()
  • <external-media-path/> --> Context.getExternalMediaDirs()

Markdown文本 + 超链接混排,实现优雅界面

每一个版本都可以提炼一些简短的介绍,然后在检测更新的时候一起获取,显示出来。

建议用Markdown格式写更新日志,Markdown 之简洁优雅足以胜任一定的生产力。

有许多开源组件可以展示Markdown,比如io.noties.markwon或者org.commonmark,前者体积较大、更加完善,后者更简单,但只是转换为html,需要再配合Html.fromHtml转换Spannable成才行

而安卓的Textview虽然支持各种图文混排,但有一些bug,比如设置linkedmovement后、再设置文本可选,会导致滚动点击时随机崩溃。

两个办法解决,一是自定义textview,try-catch包绕一些会崩溃的方法如dispatchTouchEvent(不推荐)。二是自定义触摸监听器,在onTouch中自行调用ClickableSpan、UrlSpan、LinkSpan等的的点击方式。

// 自定义触摸监听器,手动调用 ClickableSpan
TextView tv = (TextView) v;
CharSequence text = tv.getText();
if(text instanceof Spannable) {
	Spannable span = (Spannable) text;
	int x = 触摸位置_X;
	int y = 触摸位置_Y;
	
	x -= tv.getTotalPaddingLeft();
	y -= tv.getTotalPaddingTop();
	
	x += tv.getScrollX();
	y += tv.getScrollY();
	
	Layout layout = tv.getLayout();
	if(layout!=null) {
		int line = layout.getLineForVertical(y);
		int off = layout.getOffsetForHorizontal(line, x);
		ClickableSpan[] link = span.getSpans(off, off, ClickableSpan.class);
		if (link.length > 0) {
			touching = ……
			// 记录 link[0], 然后在ACTION_UP或onClick时,调用点击监听器
		}
	}
}


@Override
public void onClick(View v) {
	ClickableSpan touching = getTouchingSpan(v);
	if (touching!=null) {
		TextView widget = (TextView) v;
		Spannable span = (Spannable) widget.getText();
		if (clickInterceptor != null && clickInterceptor.onClick(widget, touching)) {
			// intentionally blank
		} else {
			touching.onClick(v);
		}
		Selection.setSelection(span,
				span.getSpanStart(touching),
				span.getSpanEnd(touching));
	}
}

轻松实现进度条

这里进度条参考的是百度第一个博客里的:给progressbar设置drawable和自定义progressbar:

purpose_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <corners android:radius="4dp"/>
            <gradient android:startColor="#EFF3F7"
                android:endColor="#EFF3F7"/>
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <scale android:scaleWidth="100%">
            <shape android:shape="rectangle">
                <corners android:radius="4dp"/>
                <gradient android:angle="45"
                    android:startColor="#42D673"
                    android:endColor="#42D673"/>
            </shape>
        </scale>
    </item>
</layer-list>

细看,里面是两层的layerdrawable,第一层是底色,另一层id/progress则是进度的颜色,不过里面的渐变色似乎没有用到啊。

然后布局代码里给seekbar添加android:progressDrawable:@drawable/purpose_drawable属性即可,非常快啊,简直不讲武德。

大佬说得好,不仅仅要创造 progress,还要创造 purpose,以后就叫做 purposeBar 吧。

Put Together

下载之时,我直接用进度条替换了对话框底部的其中一个按钮。这种替换操作用着很爽,我甚至提炼了一个方法 ViewUtils.replaceView ,一系列操作原生视图的方法 ……

public static View replaceView(View viewToAdd, View viewToRemove) {
	return replaceView(viewToAdd, viewToRemove, true);
}

public static View replaceView(View viewToAdd, View viewToRemove, boolean layoutParams) {
	ViewGroup.LayoutParams lp = viewToRemove.getLayoutParams();
	ViewGroup vg = (ViewGroup) viewToRemove.getParent();
	if(vg!=null) {
		int idx = vg.indexOfChild(viewToRemove);
		removeView(viewToAdd);
		if (layoutParams) {
			vg.addView(viewToAdd, idx, lp);
		} else {
			vg.addView(viewToAdd, idx);
		}
		removeView(viewToRemove);
	}
	return viewToAdd;
}

public static boolean removeView(View viewToRemove) {
	return removeIfParentBeOrNotBe(viewToRemove, null, false);
}

public static boolean removeIfParentBeOrNotBe(View view, ViewGroup parent, boolean tobe) {
	if(view!=null) {
		ViewParent svp = view.getParent();
		if((parent!=svp) ^ tobe) {
			if(svp!=null) {
				((ViewGroup)svp).removeView(view);
				//CMN.Log("removing from...", svp, view.getParent(), view);
				return view.getParent()==null;
			}
			return true;
		}
	}
	return false;
}

效果图:
请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL8.0分析查询语句EXPLAIN

文章目录学习资料分析查询语句EXPLAINidselect_typepartitions&#xff08;可略&#xff09;type【重点】possible_keys和keykey_len【重点】refrows【重点】filteredExtra【重点】EXPLAIN四种输出格式传统格式JSON格式SHOW WARNINGS的使用学习资料 【MySQL数据库教程天花板&a…

《深度学习进阶 自然语言处理》第五章:RNN通俗介绍

文章目录5.1 概率和语言模型5.1.1 概率视角下的word2vec5.1.2 语言模型5.1.3 将CBOW模型用作语言模型的效果怎么样&#xff1f;5.2 RNN5.2.1 循环神经网络5.2.2 展开循环5.2.3 Backpropagation Through Time5.2.4 Truncated BPTT5.2.5 Truncated BPTT的mini-batch学习5.3 RNN的…

会话跟踪技术。

目录 一、会话跟踪技术 二、Cookie 介绍 1、Cookie 基础 2、Cookie 使用细节 三、Session 介绍 1、Session 基本介绍 2、Session的原理分析 3、Session的使用细节 一、会话跟踪技术 ▶ 会话 会话:用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&a…

SAP 直接外部数据库技术配置手册-Oracle

一、操作步骤: 1、SAP Basis配置TNS文件:tnsnames.ora 事务码AL11下的 DIR_SETUPS变量D:\usr\sap\<SID>\SYS\profile双击进入文件路径oracle可以查看到文件 tnsnames.ora (不是路径D:\oracle\<SID>\102\NETWORK\ADMIN下的tnsnames.ora文件),加入如下信息(…

cubeIDE开发, stm32的WIFI通信设计(基于AT指令)

一、stm32的WIFI配置 通常WIFI模块就是一个独立的单片机&#xff0c;只是内置了WFIF通信软件的单片机&#xff0c;并该通信软件提供了AT通信指令集给开发人员&#xff0c;基于这些指令集我们就可以针对项目需要进行二次集成开发出所需的业务应用软件。 本文本文采用的开发板是s…

一、什么是计算机网络

1.1 概述 信件的要素&#xff1a; 打电话时包括连接和接通过程&#xff0c;要关注包括拨打者的状态和接听者的状态&#xff0c;称为TCP连接。发短信时只要发送者将短信发送出去即可&#xff0c;是否被接收或者发送的过程中是否有丢失这些都不关注&#xff0c;称之为UDP连接。计…

CentOS7安装jdk

文章目录前言准备工作一、将jdk的压缩文件传递到虚拟机里面二、解压缩三、配置环境变量前言 在大数据的技术中&#xff0c;Linux的环境是基础&#xff0c;jdk则是这些大数据工具的基础&#xff0c;在这篇博文中&#xff0c;我们主要介绍如何在Linux环境里安装jdk&#xff0c;以…

MySQL8.0优化 - 索引的数据结构

文章目录学习资料索引的数据结构B树常见索引概念聚簇索引特点优点缺点限制二级索引&#xff08;辅助索引、非聚簇索引&#xff09;回表联合索引Innodb的B树索引注意事项1、根页面位置万年不动2、内节点中目录项记录的唯一性3、一个页面最少存储2条记录索引的代价学习资料 【My…

Python可视化必备,在Matplotlib/Seaborn中轻松玩转图形拼接!

数据展示时&#xff0c;在同一页面上混合排版多个图形是一种常见的用法。 本次分享一个Python轮子patchworklib&#xff1a; 通过|、/轻松实现图形排列&#xff1b;比matplotlib、seaborn等自带子图功能更加灵活&#xff1b;灵感源于R中的patchwork。目录 在Matplotlib中使用…

【Java学习笔记】第四章 面向对象编程三部曲(中)

【Java学习笔记】第四章 面向对象编程三部曲&#xff08;上&#xff09; 【Java学习笔记】第四章 面向对象编程三部曲&#xff08;中&#xff09; 【Java学习笔记】第四章 面向对象编程三部曲&#xff08;下&#xff09; 文章目录5. 面向对象编程&#xff08;中&#xff09;5…

gdb调试 入门

程序的调试过程主要有&#xff1a;单步执行&#xff0c;跳入函数&#xff0c;跳出函数&#xff0c;设置断点&#xff0c;设置观察点&#xff0c;查看变量。 You can run "gdb" with no arguments or options; but the most usualway to start GDB is with one argume…

ANDROID ROOT FIDDLER HTTPS 抓包

参考 adb 修改手机代理方式_userwyh的博客-CSDN博客_adb shell settings put global http_proxy 手机模拟器安装证书并抓包_虚无-缥缈的博客-CSDN博客_模拟器安装证书 安卓手机使用adb添加系统证书方法 - 知乎 设置设备代理&#xff08;需要ROOT 设置代理&#xff1a; adb…

【重新安装Anaconda心得】

文章目录&#xff08;一&#xff09;环境变量设置&#xff08;二&#xff09;Anaconda添加镜像源【可以使用境外流量不用添加】&#xff08;三&#xff09;创建虚拟环境的细节&#xff08;四&#xff09;补充&#xff1a;conda的常用命令&#xff08;一&#xff09;环境变量设置…

算法篇------贪心1

文章目录贪心的概念题目1------经典的选择排序&#xff08;简单&#xff09;题目2----平衡字符串&#xff08;简单&#xff09;题目3---买卖股票的最佳时间(中等)题目4------跳跃游戏(中等)题目5-------钱币找零题目6------多机调度的问题贪心的概念 什么是贪心算法&#xff1…

ATF源码篇(九):docs文件夹-Components组件(8)固件升级

固件更新&#xff08;FWU&#xff09; 本文档描述了TF-A中可用的各种固件更新&#xff08;FWU&#xff09;机制的设计。 1、PSA固件更新&#xff08;PSA FWU&#xff09;-平台安全架构2、TBBR固件更新&#xff08;TBBR FWU&#xff09;-可信板引导要求 PSA固件更新实施了同名…

[附源码]java毕业设计课程作业管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

vtk拾取器-vtkAbstractPicker/vtkCellPicker

创建了VTK社区&#xff0c;欢迎大家加入雪易社区-CSDN社区云 简介&#xff1a;此文主要介绍vtk中的拾取器的用法&#xff0c;若能为各位小伙伴解决一些困扰就更好了。非常欢迎各位小伙伴指正并补充。 1. vtkAbstractPicker vtkAbstractPicker是vtk拾取器的基类&#xff0c;为…

2022下半年《软考-系统架构设计师》备考经验分享

前言 我参加了2022年11月份的《软考-系统架构设计师》考试&#xff0c;在一个多月的备考之中我总结了一些学习经验和答题技巧&#xff0c;现毫无保留的分享给大家&#xff0c;希望对报考的同学们有所帮助。彩蛋&#xff1a;关注我的公众号【劼哥舍】&#xff0c;回复“软考”即…

dubbo:docker安装dubbo-admin

0.引言 我们在搭建dubbo框架时&#xff0c;需要安装一个dubbo-admin来管理服务已经配置文件&#xff0c;今天我们来看看如何通过docker快速搭建一个dobbo-admin 1. 安装 1、首先到dockerhub上搜索dubbo-admin的镜像源 2、可以看到两个引用较高的镜像源&#xff0c;第一个是a…

SpringCloud和SpringBoot在调Feign传文件时的异常汇总及解决办法

主要记录SpringCloud在调Feign传文件时的问题&#xff1a; 1.&#xff08;按注意点2改正即可&#xff09; Current request is not a multipart request&#xff08;按注意点2改正即可&#xff09; 2.&#xff08;按注意点3处理即可&#xff09; The field files exceeds it…