Программно запустить установку APK из внутренней памяти приложения в Android 5.0–6.0

Программно установить apk в Android 7/api24 отвечает аналогично вопрос, но я хочу программно установить APK из внутренней памяти приложения (на SD-карте) и в Android 5.0 - 6.0.

Если бы это не было из внутренней памяти приложения, я мог бы startActivity() с намерением, действие которого ACTION_VIEW и чей URI данных имеет схему file:// (используя Uri.fromFile()). Но я пробовал это, и это не будет работать с внутренней памятью приложения, которая не читается другими приложениями. Я основываюсь на таких сообщениях журнала, как

W/asset: Asset path /storage/C5DF-1113/Android/data/com.example/files/foo.apk is neither a directory nor file (type=1).
W/InstallFlowAnalytics: Failed to hash APK contents
  java.io.FileNotFoundException: /storage/C5DF-1113/Android/data/com.example/files/foo.apk: open failed: ENOENT (No such file or directory)

Я пробовал создавать подклассы ContentProvider, используя схему content://. Но, по-видимому (и см. ответ CommonsWare здесь), установщик пакетов не поддерживает схему content:// до Android 7.0. В Android 6.0 я получаю сообщения журнала, подобные следующим:

I/ActivityManager: START u0 {act=android.intent.action.VIEW dat=content://com.example.provider/internal/foo.apk typ=application/vnd.android.package-archive flg=0x10000001} from uid 10159 on display 0
E/Updates: installNewApk
     android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=content://com.example.provider/internal/foo.apk typ=application/vnd.android.package-archive flg=0x10000001 }
        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)

где единственная очевидная разница между намерением, которое соответствует установщику пакета, и намерением, которое не соответствует, заключается в изменении URI данных с file://... на content://....

В другом месте есть ответы по автоматической установке приложения, что не является требованием для моего проекта. Существуют также решения, использующие отражение для доступа к недокументированным функциям или требующие рутирования устройства, но эти методы слишком ненадежны или неудобны для пользователя для этого проекта.

Существует также возможность скопировать APK из внутреннего хранилища приложения во внешнее хранилище перед запуском установщика. Возможно, нам придется пойти туда, но причина, по которой APK находится во внутренней памяти, в первую очередь заключается в целях конфиденциальности и безопасности. (Я понимаю, что это далеко не пуленепробиваемый вариант, но это лучше, чем ничего, и именно этого хотят наши клиенты.)

Я готов сделать условное на основе текущей версии Android, но на данный момент я все еще не знаю, что делать в 5.0–6.0.

Спасибо за любые предложения.


person LarsH    schedule 02.11.2017    source источник
comment
внутренняя память обычно зарезервирована для таких вещей, как getFilesDir(). SD-карту лучше называть съемным хранилищем. Что касается вашей проблемы, AFAIK, вам не повезло - APK должен быть на внешнем хранилище (getExternalFilesDir() и т. д.) до Android 7.0. Я жаловался на это громко, но безрезультатно.   -  person CommonsWare    schedule 02.11.2017
comment
@CommonsWare: Спасибо за ответ. Что касается съемного хранилища, я согласен, что это хороший термин для хранения на SD-карте, но наиболее важным отличием этого вопроса является то, что оно не находится в общедоступной области. Если бы это было так, я мог бы просто использовать Uri.fromFile() без ContentProvider (до 7.0). Насколько я знаю, тот факт, что он находится на съемном носителе, на этот вопрос не влияет.   -  person LarsH    schedule 03.11.2017
comment
Насколько мне известно, тот факт, что он находится на съемном носителе, на самом деле не влияет на этот вопрос - начиная с Android 4.4 приложения не имеют произвольного доступа для чтения/записи к съемному хранилищу, поэтому Uri.fromFile() не работает для вас. Только у вас есть доступ к этому каталогу; другие приложения, включая установщик, нет. Вы столкнетесь с той же проблемой, используя getFilesDir(), поскольку другие приложения не имеют доступа к этому местоположению. И вы не можете использовать content в качестве схемы до версии 7.0, что оставляет вам внешнее хранилище... и все, насколько я знаю.   -  person CommonsWare    schedule 03.11.2017
comment
@CommonsWare: верно, но приложения также не имеют произвольного доступа для чтения/записи к несъемному хранилищу. Вот почему я говорю, что это на самом деле не влияет на этот вопрос.   -  person LarsH    schedule 03.11.2017
comment
но приложения также не имеют произвольного доступа для чтения/записи к несъемному хранилищу — да, но приложение-установщик имеет доступ к внешнему хранилищу, но не к вашему конкретному сегменту съемного хранилища или к вашему конкретному сегменту внутреннего хранилища. Следовательно, в вашем сценарии существует очень большая разница между внешним хранилищем и съемным хранилищем.   -  person CommonsWare    schedule 03.11.2017
comment
Итак, вы акцентируете внимание на разнице между съемным хранилищем и внешним хранилищем, а не между съемным и несъемным хранилищем.   -  person LarsH    schedule 03.11.2017
comment
Ну, у всех трех есть свои особенности. Внешнее хранилище — единственное, которое может использоваться совместно всеми приложениями или, по крайней мере, тем подмножеством, которое запросило разрешение у пользователя.   -  person CommonsWare    schedule 03.11.2017