Перейти к содержанию
Fire Monkey от А до Я
  • 0

Свой APK updater. Использование Fileprovider


krapotkin

Вопрос

У меня в работе два приложения, и оба они не предназначены для Play market, так как имеют ограниченный круг использования, по сути, чисто внутрикорпоративные. Так что нежелательно и выкладывание их и на альтернативные магазины приложений. 

Автоматически возникает вопрос обновления. Если в  первый раз мы можем установить приложение сами при помощи админов, то обновлять их не так просто. А контингент пользователей не справится с "скачайте APK по ссылке, найдите, куда его скачал браузер, и запустите вручную именно последний скачанный, а не какой попало"...

Простейший способ - дать приложению скачать свежую копию с сайта и натравить на полученный файл системный инсталлер.

Вот только свежие Andoird делать это напрямик запрещают. Нужен filepropvider. Целый день шуровал по мануалам и YT,

Вот то что получилось  в результате.

Если у вас 10.3.3 как у меня, уже можно не вносить <provider>...</provider> в манифест и свой файл file_paths.xml (или как вам его советуют назвать в интернетах) в деплой.

Теперь все это делается хоть несколько странно и однобоко, но автоматически, путем установки галочки Secure File Sharing

image.png.6d59f678ef743d8012a1ca00933e035f.png

после этого в манифесте автоматически пропишется один из вариантов размещения файлов, которые вы можете найти в интернете. Используется алиас external-path

файл, показанный на рисунке, создается автоматически самой делфи.

image.png.25bf8a8abd95f71da5ee2f95005d82be.png

теперь остается отгадать, какой путь реально подставится вместо "."

Как показала практика, все пути выглядят не так, как кажется, если исходить из простого здравого смысла. Целый день использования GetHomeDir и других полезных методов TPath завел меня совсем в тупик.

Оказалось все проще (?)

  st:TMemoryStream;
  OutputDir: JFile;
  ApkFile: JFile;
  ApkUri: Jnet_Uri;
  path, filename: string;
...    
    OutputDir := TAndroidHelper.Context.getExternalCacheDir();
    path := JStringToString(OutputDir.getAbsolutePath);
    filename := path+'/ASDroid2.apk';
    ApkFile := TJfile.JavaClass.init( StringToJstring(filename));
    FApkUri := TAndroidHelper.JFileToJURI(ApkFile);
    st.Position := 0;
    st.SaveToFile(filename);

обратите внимание, в provider_paths мы задаем external-paths, а в коде ищем ExternalCacheDir.!!!  (For.Unbelievably.Creative.Knowers!)

Потом все просто. FApkUri передаем в интент и запускаем 

итоговый код примерно таков. (скачивание в потоке с использованием небольшого собственного API, но там ничего важного, можно не обращать внимания)

procedure TasdSettingsFrame.bDownloadClick(Sender: TObject);
begin
{$IFDEF ANDROID}
  bDownload.Enabled := False;
  DownloadAndRun();
{$ENDIF}
end;

{$IFDEF ANDROID}
procedure TasdSettingsFrame.DownloadAndRun();
begin
  ttask.Run(procedure
  var
    aapi:TasdAPI;
    st:TMemoryStream;
    OutputDir: JFile;
    ApkFile: JFile;
    ApkUri: Jnet_Uri;
    path, filename: string;
  begin
    st := TMemoryStream.Create;
    aapi := TasdAPI.Clone(_API);
    try
      aapi.OnReceiveData := OnReceiveData;
      aapi.getApk(st);
      if aapi.Err.Code=0 then
      begin
        OutputDir := TAndroidHelper.Context.getExternalCacheDir();
        path := JStringToString(OutputDir.getAbsolutePath);
        filename := path+'/ASDroid2.apk';
        ApkFile := TJfile.JavaClass.init( StringToJstring(filename));
        FApkUri := TAndroidHelper.JFileToJURI(ApkFile);
        st.Position := 0;
        st.SaveToFile(filename);

        TThread.Synchronize(nil,procedure begin
          bDownload.Enabled := true;
          StartActivity(FApkUri);
        end);
      end;
    finally
      st.Free;
      aapi.Free;
    end;
  end);
end;

procedure StartActivity(ApkUri: Jnet_Uri);
var
  Intent: JIntent;
begin
  Intent := TJIntent.Create();
  Intent.setAction(TJIntent.JavaClass.ACTION_VIEW);
  Intent.addFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK
      or TJIntent.JavaClass.FLAG_ACTIVITY_CLEAR_TOP
      or TJIntent.JavaClass.FLAG_GRANT_WRITE_URI_PERMISSION
      or TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
  Intent.setDataAndType(apkuri, StringToJString('application/vnd.android.package-archive'));
  TAndroidHelper.Activity.startActivity(Intent);
end;

procedure TasdSettingsFrame.OnReceiveData(const Sender: TObject; AContentLength: Int64;
AReadCount: Int64; var Abort: Boolean);
begin
  tthread.Synchronize(nil, procedure
  begin
    pb1.Max := AContentLength;
    pb1.Value := AReadCount;
  end);
end;
{$ENDIF}

Вопросы остались конечно, почему так странно с каталогами, но выяснять пока нет желания. Работает - не трожь.

Всем удачи.

UPD.

Для того, чтобы системный инсталлер запускался, нужно не забыть отметить еще одну галочку

image.png.0570ac948ec20d139fb32e8662c769b9.png

 

 

 

 

 

Изменено пользователем krapotkin
требуется дополнительное разрешение
Ссылка на комментарий

Рекомендуемые сообщения

  • 0

В последних андройдах (9, 10 точно) установка из неисвестных источников настраивается для каждого отдельного приложения, пытающегося произвести установку. Здесь учитывается этот момент?

Ссылка на комментарий
  • 0

От программиста никаких действий не требуется. Система любезно сообщает, что откуда попало не ставит, но вот вам переход прямо на нужную настройку. Переходим, включаем, нажимаем back и продолжаем установку...

Ссылка на комментарий
  • 0

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...