Delphi Android APP 自动更新:

这篇博客介绍了如何使用RADStudio10.3.3在Android 10上实现Delphi开发的App自动更新功能。作者详细阐述了检查更新、下载安装的过程,包括ASP服务器配置、JSON文件处理、IIS设置、AndroidManifest.xml修改等步骤,并提供了关键代码片段。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

资料下载地址:   https://download.csdn.net/download/sczyq/12573399

===========================================================

Delphi Android APP 自动更新: 使用 RADStudio10.3.3 并在 Android 10 测试通过

我的理想 : 只需要将新版的APP的安装APK文件复制到网站的下载地址里, 其他的什么也不用做

编写过程 : 艰难地从 N 个坑里爬出来后, 终于惊险地过关 ^_^

原理:  

        过程: function CheckInstalled : Boolean; 
            检查 UCFG_APP 与 UCFG_APK 的值, 一致就表示已经成功安装, 不一致就继续检查
            通过 APP 与 APK 版本检查是否一致, 如果是, 设标志 UCFG_APP=UCFG_APK, 并删除 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 服务器根目录

fso.json

{"path" : <%
	path = Request("p")
%>"<%=path%>", "list" : [<%
	path = server.MapPath(path)
	Dim fso
	Set fso = CreateObject("Scripting.FileSystemObject")
	if fso.FileExists(path) then
		Dim vf
		Set vf = fso.GetFile(path)
%>{"name" : "<%=vf.name%>", "size" : "<%=vf.size%>", "attr" : "<%=vf.Attributes%>", "time" : "<%=vf.DateLastModified%>"}<%
		Set vf = Nothing
	end if
	Set fso = Nothing
%>]}

1.2 修改 IIS 配置 

    [处理程序映射] - 添加脚本映射 
      请求路径=*.json
      可执行文件=%windir%\system32\inetsrv\asp.dll
      名称=JSONClassic
      请求限制 设成与 ASPClassic 的一样

        [MIME类型] - 添加, (如果已做请忽略)
            文件扩展名:   .apk
            MIME类型:     application/vnd.android.package-archive


1.3 修改 ASP 日期时间格式:

  打开注册表,进入到[HKEY_USERS\.DEFAULT\Control Panel\International],然后
  将键 sDate 的值由 / 改为 -
  将键 sShortDate 的值由 yyyy/M/d 改为 yyyy-MM-dd
  重启 IIS 即可(开始-运行-cmd 中输入iisreset,回车)。

1.4 测试 fso.json 效果   http://127.0.0.1/fso.json?p=/app/android/myapp.apk
  
  返回结果数据是:
  
  {"path" : "/app/android/myapp.apk", "list" : [{"name" : "myapp.apk", "size" : "12345678", "attr" : "32", "time" : "2020-06-30 12:00:00"}]}  
  
  就 OK 了.

======================================
   
2. 修改你的 APP 项目:

2.1  加入 AndroidUpdating.pas 到你的 APP 项目

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 HTTPRequestError(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
  UCFG_GET = 'get';   //  检查日期, 同一天只成功检查一次
  UCFG_APK = 'apk';   //  APK 文件时间, 获取成功后写入
  UCFG_APP = 'app';   //  APP 安装成功后, 校验一致则等于 UCFG_APK

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 := HTTPRequestError;

  //  当天未获取过更新
  if StrToIntDef(FConfigure.Values[UCFG_GET], 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.HTTPRequestError(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('list');
  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');				
        //  注意: S 的值
        //  由 fso.json 返回的文件日期必须是 'YYYY-MM-DD HH:NN:SS'
        //  如果不一样, 需要处理成这种格式
        FConfigure.Values[UCFG_GET] := IntToStr(Trunc(Now));
        FConfigure.Values[UCFG_APK] := S;
        FConfigure.SaveToFile(FConfigureFile);
        if not SameText(FConfigure.Values[UCFG_APP], S) then
        begin
          if FileExists(FLocalFile) then
          begin
            S := FormatDateTime('YYYY-MM-DD HH:NN:SS', TFile.GetLastWriteTime(FLocalFile));
            if SameText(FConfigure.Values[UCFG_APK], 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
  D : TDateTime;
  F : TFormatSettings;
  FS : TFileStream;

begin
  FNetHTTP.OnRequestCompleted := nil;
  try
    FS := TFileStream.Create(FLocalFile, fmCreate);
    try
      AResponse.ContentStream.Position := 0;
      FS.CopyFrom(AResponse.ContentStream, AResponse.ContentStream.Size);
    finally
      FreeAndNil(FS);
			F.DateSeparator := '-';
			F.TimeSeparator := ':';
			F.ShortDateFormat := 'yyyy-m-d';
			F.LongDateFormat := 'yyyy-mm-dd';
			F.ShortTimeFormat := 'h:n:s';
			F.LongTimeFormat := 'hh:nn:ss';
      if TryStrToDateTime(FConfigure.Values[UCFG_APK], D, F) 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[UCFG_APP], FConfigure.Values[UCFG_APK]);

  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[UCFG_APP] := FConfigure.Values[UCFG_APK];
        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.

2.2  你的 APP 主窗口(TMainForm)引用 AndroidUpdating 单元

uses
    ...,  
    AndroidUpdating;
   
    private
        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. 收工!

 

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值