Delphi Android APP 自动更新: 使用 RADStudio10.3.3 并在 Android 10 测试通过
Delphi Android APP 自动更新: 使用 RADStudio10.3.3 并在 Android 10 测试通过
我的理想 : 只需要将新的 APP 复制到网站的下载地址里, 其他的什么也不想做
编写过程 : 艰难地从 N 个坑里爬出来后, 终于惊险地过关 ^_^
原理:
过程: function CheckInstalled : Boolean;
检查 APP_TIME 与 APK_TIME 的值, 一致就表示已经成功安装, 不一致就继续检查
通过 APP 与 APK 版本检查是否一致, 如果是, 设标志 APP_TIME=APK_TIME, 并删除 APK 文件
非常重要: Options -> Application -> Version Info -> versionName 版本号不变即使最新也不会执行安装
A. 每天的首次开启 APP 时, 则是这样做(必须获取信息成功, 否则每次都是首次)
1. 通过 fso.json 获取 APK 的信息, 执行 2
2. 检查本地 download 目录 APK 文件, 时间不一致就执行3, 否则直接执行4
3. 下载, 成功后修改 apk 文件时间, 与服务器一致, 再执行 4
4. CheckInstalled 检查是否成功安装, 未成功安装的就调用 APK 安装
B. 每天的再次开启 APP 时, 只执行 CheckInstalled 检查
========================================
开工: 如果你的APP下载地址是 http://127.0.0.1/app/android/myapp.apk
========================================
1. 整理 ASP 服务器 (本人是Win7)
1.1 复制 fso.json 到 ASP 服务器根目录
1.2 修改 IIS 配置
[处理程序映射] - 添加脚本映射
请求路径=*.json
可执行文件=%windir%\system32\inetsrv\asp.dll
名称=JSONClassic
请求限制 设成与 ASPClassic 的一样
[MIME类型] - 添加, (如果已做请忽略)
文件扩展名: .apk
MIME类型: application/vnd.android.package-archive
1.3 测试 fso.json 效果 http://127.0.0.1/fso.json?p=/app/android/myapp.apk
结果是:
{"path" : "/app/android/myapp.apk", "filelist" : [{"name" : "myapp.apk", "size" : "123456", "attr" : "32", "time" : "2020/05/22 12:00:00"}]}
就可以了.
======================================
2. 修改你的 APP 项目:
2.1 加入 AndroidUpdating.pas 到你的 APP 项目
2.2 你的 APP 主窗口(TMainForm)引用 AndroidUpdating 单元
uses
...,
AndroidUpdating;
provate
FUpdating : TAndroidUpdating;
// 释放指针过程: NotifyFreeUpdating
procedure TMainform.NotifyFreeUpdating;
begin
FreeAndNil(FUpdating); // 同步释放: (不建议, 等于是在类内部过程中调用释放)
// 异步释放: (建议) 通过 TTimer 来进行, 过程自己去写
end;
// 执行版本检查与自动更新, 写到最后一行比较好
procedure TMainform.FromCreate(Sender : TObject);
begin
...
FUpdating := TAndroidUpdating.Create(Self, 'http://127.0.0.1/app/android/myapp.apk', NotifyFreeUpdating);
end;
2.3 修改 APP 项目设置 (建议在 All Configurations - Android 修改) 以下必须要勾选
Options - Application - Entitlement List - Secure File Sharing
Options - Application - Uses Premissions - Write external stoage
Options - Application - Uses Premissions - Request install packages
2.4 修改 APP 项目文件夹内的 AndroidManifest.template.xml 在 Application 中加入一行
android:usesCleartextTraffic="true"
即:
<application
...
android:usesCleartextTraffic="true">
说明: 如果是 http:// 必须加, 如果是 https:// 就不用, 自动去选
======================================
3. 收工!
{=====================================
功能: Delphi Android APP Auto Update
版权: csdn 账号: sczyq QQ :79896009
授权: 免费, 如果你觉得好, 就给我点赞
=====================================}
unit AndroidUpdating;
interface
uses
System.Classes,
System.Net.URLClient,
System.Net.HttpClient,
System.Net.HttpClientComponent;
type
TAndroidUpdating = class(TComponent)
private
FNetHTTP : TNetHTTPClient;
FHome : String;
FRemoteFile : String;
FLocalFile : String;
FConfigureFile : String;
FConfigure : TStringList;
FNotifyFree : TThreadMethod;
procedure HTTPRequestDownload(const Sender: TObject; const AResponse: IHTTPResponse);
procedure HTTPRequestFileInfo(const Sender: TObject; const AResponse: IHTTPResponse);
procedure HTTPError(const Sender: TObject; const AError: string);
function CheckInstalled : Boolean;
procedure DoNotifyFree;
procedure ExecuteInstall;
public
constructor Create(AOwner : TComponent; APath : String; AFreeEvent : TThreadMethod); reintroduce;
destructor Destroy; override;
end;
implementation
uses
System.Types, System.SysUtils, System.JSON, System.IOUtils,
System.Generics.Collections,
Androidapi.Helpers,
Androidapi.JNI.App,
Androidapi.JNI.Net,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.Support,
Androidapi.JNI.GraphicsContentViewText;
const
APK_DATE = 'apk.date'; // 最后检查日期, 同一天只成功检查一次
APK_TIME = 'apk.time'; // APK 文件时间, 获取成功后写入
APP_TIME = 'app.time'; // APP 安装成功后, 校验一至则等于 APK_TIME
constructor TAndroidUpdating.Create(AOwner : TComponent;
APath : String; AFreeEvent : TThreadMethod);
var
I : Integer;
begin
inherited Create(AOwner);
FConfigure := TStringList.Create;
I := APath.IndexOf('://');
if I > 0 then Inc(I,4)
else
begin
APath := 'http://' + APath;
I := 7;
end;
I := APath.IndexOf('/', I);
if I > 0 then
begin
FHome := APath.Remove(I);
FRemoteFile := APath.Remove(0, I);
end
else FHome := APath;
FLocalFile := TPath.Combine(TPath.GetDownloadsPath, ExtractFileName(FRemoteFile));
FConfigureFile := TPath.Combine(TPath.GetHomePath, 'updating.configure');
if FileExists(FConfigureFile) then
FConfigure.LoadFromFile(FConfigureFile);
FNetHTTP := TNetHTTPClient.Create(Self);
FNetHTTP.ConnectionTimeout := 3000;
FNetHTTP.ResponseTimeout := 8000;
FNetHTTP.Asynchronous := True;
FNetHTTP.OnRequestError := HTTPError;
// 当天未获取过更新
if StrToIntDef(FConfigure.Values[APK_DATE], 0) < Trunc(Now) then
begin
FNetHTTP.OnRequestCompleted := HTTPRequestFileInfo;
FNetHTTP.Get(FHome + '/fso.json?p=' + FRemoteFile);
end
else
begin
CheckInstalled;
DoNotifyFree;
end;
end;
destructor TAndroidUpdating.Destroy;
begin
FreeAndNil(FNetHTTP);
FreeAndNil(FConfigure);
inherited;
end;
procedure TAndroidUpdating.HTTPError(const Sender: TObject; const AError: string);
begin
FNetHTTP.OnRequestCompleted := nil;
DoNotifyFree;
end;
procedure TAndroidUpdating.HTTPRequestFileInfo(const Sender: TObject;
const AResponse: IHTTPResponse);
function ItemValue(JSON : TJSONValue; Item : String) : String;
var
V : TJSONValue;
begin
SetLength(Result, 0);
V := TJSONObject(JSON).GetValue(Item);
if Assigned(V) then
Result := V.Value;
end;
var
I : Integer;
JV : TJSONValue;
JA : TJSONArray;
S : String;
IsDownload : Boolean;
begin
IsDownload := False;
FNetHTTP.OnRequestCompleted := nil;
JV := TJSONObject.ParseJSONValue(AResponse.ContentAsString(TEncoding.UTF8));
JV := TJSONObject(JV).GetValue('filelist');
if Assigned(JV) and (JV is TJSONArray) then
begin
JA := TJSONArray(JV);
for I := 0 to JA.Count - 1 do
begin
if SameText(ItemValue(JA.Items[I], 'name'), ExtractFileName(FLocalFile)) then
begin
S := ItemValue(JA.Items[I], 'time');
if S.IndexOf('/') > 0 then
S := StringReplace(S, '/', '-', [rfReplaceAll]);
FConfigure.Values[APK_DATE] := IntToStr(Trunc(Now));
FConfigure.Values[APK_TIME] := S;
FConfigure.SaveToFile(FConfigureFile);
if not SameText(FConfigure.Values[APP_TIME], S) then
begin
if FileExists(FLocalFile) then
begin
S := FormatDateTime('YYYY-MM-DD HH:NN:SS', TFile.GetLastWriteTime(FLocalFile));
if SameText(FConfigure.Values[APK_TIME], S) then
ExecuteInstall
else TFile.Delete(FLocalFile);
end;
if not FileExists(FLocalFile) then
begin
IsDownload := True;
FNetHTTP.OnRequestCompleted := HTTPRequestDownload;
FNetHTTP.Get(FHome + FRemoteFile);
end;
end;
break;
end;
end;
end;
if not IsDownload then
DoNotifyFree;
end;
procedure TAndroidUpdating.HTTPRequestDownload(const Sender: TObject;
const AResponse: IHTTPResponse);
var
F : TFileStream;
D : TDateTime;
begin
FNetHTTP.OnRequestCompleted := nil;
try
F := TFileStream.Create(FLocalFile, fmCreate);
try
AResponse.ContentStream.Position := 0;
F.CopyFrom(AResponse.ContentStream, AResponse.ContentStream.Size);
finally
FreeAndNil(F);
if TryStrToDateTime(FConfigure.Values[APK_TIME], D) then
TFile.SetLastWriteTime(FLocalFile, D);
end;
ExecuteInstall;
except
TFile.Delete(FLocalFile);
DoNotifyFree;
end;
end;
function TAndroidUpdating.CheckInstalled : Boolean;
var
pm : JPackageManager;
InfoP, InfoA : JPackageInfo;
begin
Result := SameText(FConfigure.Values[APP_TIME], FConfigure.Values[APK_TIME]);
if not Result then
if FileExists(FLocalFile) then
begin
pm := TAndroidHelper.Context.getPackageManager();
InfoP := pm.getPackageInfo(TAndroidHelper.Context.getPackageName, 0);
InfoA := pm.getPackageArchiveInfo(StringToJString(FLocalFile), TJPackageManager.JavaClass.GET_ACTIVITIES);
if Assigned(InfoP) and Assigned(InfoA) then
begin
if SameText(JStringToString(InfoP.versionName), JStringToString(InfoA.versionName)) then
begin
FConfigure.Values[APP_TIME] := FConfigure.Values[APK_TIME];
FConfigure.SaveToFile(FConfigureFile);
TFile.Delete(FLocalFile);
Result := True;
end;
end;
end;
end;
procedure TAndroidUpdating.DoNotifyFree;
begin
if Assigned(FNotifyFree) then
FNotifyFree;
end;
procedure TAndroidUpdating.ExecuteInstall;
var
LFile: JFile;
LIntent: JIntent;
LNet_Uri : JNet_Uri;
begin
if not CheckInstalled then
begin
LFile := TJFile.JavaClass.init(StringToJString(FLocalFile));
LIntent := TJIntent.Create;
if TOSVersion.Check(8, 0) then
LIntent.setAction(TJIntent.JavaClass.ACTION_INSTALL_PACKAGE)
else LIntent.setAction(TJIntent.JavaClass.ACTION_VIEW);
LIntent.addFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK);
if TOSVersion.Check(7, 0) then
begin
LIntent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
LNet_Uri := TAndroidHelper.JFileToJURI(LFile);
end
else LNet_Uri := TJnet_Uri.JavaClass.fromFile(LFile);
LIntent.setDataAndType(LNet_Uri, StringToJString('application/vnd.android.package-archive'));
TAndroidHelper.Activity.startActivity(LIntent);
end;
DoNotifyFree;
end;
end.