Можем ли мы установить APK из ContentProvider?

Я работаю над библиотекой, позволяющей пользователям самообновлять, для тех, которые распространяются за пределами Android Market.

Мой первоначальный план состоял в том, чтобы включить код, который будет загружать файл APK во внутреннюю память, а затем установить его там через ContentProvider и content:// Uri. Однако, когда я это пробовал, система установки сбрасывала предупреждение "Пропустить dir:" в LogCat и не смогла его фактически установить. Как только я переключился на загрузку APK во внешнее хранилище и с помощью file:// Uri с установщиком ACTION_VIEW Intent, он сработал.

Сообщение "Пропуск dir:", по-видимому, регистрируется parsePackage() в PackageParser, что, по-видимому, предполагает, что оно работает с File. Это означает, что мы не можем использовать значения content:// Uri.

Кто-нибудь успешно использовал ACTION_VIEW на application/vnd.android.package-archive Intent с content:// Uri? Если да, был ли какой-то конкретный трюк в настройке ContentProvider, который заставил его работать?

Спасибо!

Ответ 1

Я бы предположил, что это невозможно, поскольку API Java, похоже, не позволяет этого. ContentProvider openFile() возвращает a ParcelFileDescriptor, из которого вы можете получить java.io.FileDescriptor. Затем вы можете использовать этот FileDescriptor, чтобы открыть либо FileInputStream, либо FileOutputStream. К сожалению, вы не можете использовать его для открытия RandomAccessFile (несмотря на то, что RandomAccessFile работает над дескрипторами, точно так же, как и другие, конструктор, который вам нужен, просто отсутствует в API).

Поскольку файлы APK являются ZIP файлами, которые должны быть считаны неработоспособными (вам нужно искать до конца, чтобы найти каталог файлов), я предполагаю, что для реализации установки потребуется RandomAccessFile, поэтому у него не было бы было возможно поддержать случай, который вы пытаетесь реализовать.

Ответ 2

Неверная документация для ACTION_INSTALL_PACKAGE. Он также будет принимать файлы только.

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

Предыдущий неверный ответ. В версии 4.0 и выше есть ACTION_INSTALL_PACKAGE, который примет контент://URI (JavaDoc), но до этого вы ограничены установкой через ACTION_VIEW, который предполагает, что переданный URI является файлом://URI.

Ответ 3

Я согласен с анализом Жюля, и я бы добавил конкретные уточнения:

В PackageInstallerActivity, который вызывается ACTION_VIEW на apk, это происходит в методе onCreate():

315 String apkPath = mPackageURI.getPath();
316 File apkFile = new File(apkPath);

И до этого этот метод из PackageUtil вызывается:

73 public static  PackageParser.Package getPackageInfo(Uri packageURI) {
74     final String archiveFilePath = packageURI.getPath();
75     PackageParser packageParser = new PackageParser(archiveFilePath);
76     File sourceFile = new File(archiveFilePath);
77     DisplayMetrics metrics = new DisplayMetrics();
78     metrics.setToDefaults();
79     return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
80 }

Все это имеет тенденцию подтверждать, что PackageManager ожидает только File Uris.

У вас есть журнал Skipping dir: в packageParser.parsePackage, который проверяет, является ли путь, указанный в Uri, файлом или нет.

Ответ 4

У меня это в одном из моих приложений, которое позволяет мне получить доступ к локальному хранилищу (пользовательский выбор можно выбрать до того, как у вас будет доступ;))

import java.io.*;
import android.content.*;
import android.database.*;
import android.net.*;
import android.os.*;
import android.preference.PreferenceManager;
import android.util.Log;

public class LocalFileContentProvider extends ContentProvider {
   private static final String URI_PREFIX = "content://your.content.provider.as.per.manifest";

   public static String constructUri(String url) {
       Uri uri = Uri.parse(url);
       return uri.isAbsolute() ? url : URI_PREFIX + url;
   }

   @Override
   public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {

       SharedPreferences app_preferences = PreferenceManager.getDefaultSharedPreferences(getContext());

       boolean allowLocal = app_preferences.getBoolean("allowLocalFiles", false);

        if (allowLocal) {    
            try {
                File file = new File(uri.getPath());

                if (file.isDirectory())
                    return null;

                ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
                return parcel;
            } catch (Exception e) {
                return null;
            }
        } else {
            return null;
        }

   }

   @Override
   public boolean onCreate() {
       return true;
   }

   @Override
   public int delete(Uri uri, String s, String[] as) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public String getType(Uri uri) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public Uri insert(Uri uri, ContentValues contentvalues) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

}

Мой манифест содержит

<provider android:name="it.automated.android.kiosk.se.LocalFileContentProvider"
      android:authorities="it.automated" />

а затем для запуска установки (или действия)

Intent viewIntent = new Intent(Intent.ACTION_VIEW);
viewIntent.setDataAndType(Uri.parse(url), mimeType);