Zainstaluj aplikację programowo na Androidzie

211

Chcę wiedzieć, czy można programowo zainstalować dynamicznie pobieraną aplikację z niestandardowej aplikacji na Androida.

Alkersan
źródło
Nie wiem, co oznacza „moduł ładujący dynamiczny, zależny od bieżącego środowiska użytkownika”. Odpowiedź dostarczona przez @Lie Ryan pokazuje, jak zainstalować pakiet APK pobrany w dowolny sposób.
CommonsWare

Odpowiedzi:

240

Możesz łatwo uruchomić link do sklepu Play lub monit instalacyjny:

Intent promptInstall = new Intent(Intent.ACTION_VIEW)
    .setDataAndType(Uri.parse("content:///path/to/your.apk"), 
                    "application/vnd.android.package-archive");
startActivity(promptInstall); 

lub

Intent goToMarket = new Intent(Intent.ACTION_VIEW)
    .setData(Uri.parse("https://play.google.com/store/apps/details?id=com.package.name"));
startActivity(goToMarket);

Nie można jednak instalować plików .apks bez wyraźnej zgody użytkownika ; chyba że urządzenie i twój program są zrootowane.

Lie Ryan
źródło
35
Dobra odpowiedź, ale nie koduj /sdcard, ponieważ jest to złe na Androidzie 2.2+ i innych urządzeniach. Użyj Environment.getExternalStorageDirectory()zamiast tego.
CommonsWare
3
Katalog / asset / istnieje tylko na komputerze dewelopera, gdy aplikacja jest kompilowana do pliku APK, katalog / asset / już nie istnieje, ponieważ wszystkie zasoby są spakowane w pliku APK. Jeśli chcesz zainstalować z katalogu / asset /, musisz najpierw wyodrębnić go do innego folderu.
Lie Ryan,
2
@LieRyan. Miło widzieć twoją odpowiedź. Mam zrootowane urządzenie z niestandardową pamięcią ROM. Chcę dynamicznie instalować motywy bez pytania użytkownika o naciśnięcie przycisku instalacji. Czy mogę to zrobić.
Sharanabasu Angadi
1
Wydaje się, że to już nie działa, gdy celSdk ma 25 lat. Daje to wyjątek: „android.os.FileUriExposedException: ... apk udostępniony poza aplikacją poprzez Intent.getData () ...”. Dlaczego?
programista Androida
4
@android developer: Obecnie nie mogę tego przetestować, ale na targetSdkVersion> = 24 obowiązują następujące zasady: stackoverflow.com/questions/38200282/... więc musisz użyć FileProvider.
Lie Ryan,
56
File file = new File(dir, "App.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);

Miałem ten sam problem i po kilku próbach mi się to udało. Nie wiem dlaczego, ale osobne ustawianie danych i typów spieszyło moje zamiary.

Horaceman
źródło
Nie mogę głosować wystarczająco za odpowiedzią. Z jakiegoś powodu osobne ustawienie danych zamiaru i typu MIME powoduje wyjątek ActivityNotFoundException na poziomie API 17.
Brent M. Spell
Właśnie to podniosłem. Ta forma była jedyną cholerną formą, jaką mogłem dostać do pracy. Jeszcze lata później ... co to za dziwaczny błąd? Zmarnowane godziny. Używam Eclipse (Helios), BTW.
Tam
10
@ BrentM.Spell i inni: zajrzyj do dokumentacji, zobaczysz, że za każdym razem, gdy ustawisz tylko dane typu OR, drugi zostanie automatycznie unieważniony, np .: setData()spowoduje usunięcie parametru type. MUSISZ użyć, setDataAndType()jeśli chcesz podać wartości dla obu. Tutaj: developer.android.com/reference/android/content/…
Bogdan Alexandru
41

Rozwiązania dostarczone dla tego pytania mają zastosowanie do targetSdkVersions z 23 i poniżej. W przypadku systemu Android N, tj. Interfejsu API poziomu 24 i nowszego, nie działają one jednak i występują awarie z następującym wyjątkiem:

android.os.FileUriExposedException: file:///storage/emulated/0/... exposed beyond app through Intent.getData()

Wynika to z faktu, że począwszy od Androida 24 Urizmieniło się adresowanie pobranego pliku. Na przykład plik instalacyjny o nazwie appName.apkprzechowywany w głównym zewnętrznym systemie plików aplikacji z nazwą pakietu com.example.testbyłby taki jak

file:///storage/emulated/0/Android/data/com.example.test/files/appName.apk

dla API 23i poniżej, podczas gdy coś podobnego

content://com.example.test.authorityStr/pathName/Android/data/com.example.test/files/appName.apk

za API 24i powyżej.

Więcej informacji na ten temat można znaleźć tutaj i nie zamierzam przez to przechodzić.

Aby odpowiedzieć na pytanie o targetSdkVersionz 24i powyżej, trzeba wykonać następujące czynności: Dodaj następujące do AndroidManifest.xml:

<application
        android:allowBackup="true"
        android:label="@string/app_name">
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.authorityStr"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/paths"/>
        </provider>
</application>

2. Dodaj następujący paths.xmlplik do xmlfolderu resw src, main:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="pathName"
        path="pathValue"/>
</paths>

To, pathNameco pokazano w przykładowym przykładzie treści powyżej i pathValuejest rzeczywistą ścieżką w systemie. Dobrym pomysłem byłoby umieszczenie „”. (bez cudzysłowów) dla pathValue powyżej, jeśli nie chcesz dodawać żadnego dodatkowego podkatalogu.

  1. Napisz następujący kod, aby zainstalować apk o nazwie appName.apkw podstawowym zewnętrznym systemie plików:

    File directory = context.getExternalFilesDir(null);
    File file = new File(directory, fileName);
    Uri fileUri = Uri.fromFile(file);
    if (Build.VERSION.SDK_INT >= 24) {
        fileUri = FileProvider.getUriForFile(context, context.getPackageName(),
                file);
    }
    Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
    intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
    intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive");
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    context.startActivity(intent);
    activity.finish();
    

Żadne pozwolenie nie jest również konieczne przy zapisywaniu do prywatnego katalogu własnej aplikacji w zewnętrznym systemie plików.

Napisałem tutaj bibliotekę AutoUpdate , w której użyłem powyższego.

Ali Nem
źródło
2
Zastosowałem tę metodę. Mówi jednak, że plik jest uszkodzony po naciśnięciu przycisku instalacyjnego. Jestem w stanie zainstalować ten sam plik, przesyłając przez bluetooth. dlaczego tak?
HI, gdy pojawi się błąd uszkodzenia pliku, jaki sposób transportu podałeś do swojej aplikacji, aby przynieść APK, jeśli pobierasz z serwera, sprawdź, czy opróżnić strumienie z pliku kopiowania z serwera? Ponieważ instalacja pakietu APK, który został przesłany przez działającą technologię Bluetooth, to chyba problem.
Sridhar S
2
java.lang.NullPointerException: Próba wywołania metody wirtualnej „android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData (android.content.pm.PackageManager, java.lang.String)” na odwołanie do obiektu o wartości null
Makvin
Twoja biblioteka może mieć w końcu prosty
plik
2
Dziękuję bardzo, po tygodniu zmagań z tą odpowiedzią rozwiązałem swój problem! @SridharS, wiem, że to już dawno, jak widzę, ale jeśli jesteś zainteresowany, w linii 5, powinieneś dodać .authorityStrpo context.getPackageName()tym, to powinno działać.
sehrob
30

Cóż, kopałem głębiej i znalazłem źródła aplikacji PackageInstaller ze źródła Android.

https://github.com/android/platform_packages_apps_packageinstaller

Z manifestu stwierdziłem, że wymaga pozwolenia:

    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />

Rzeczywisty proces instalacji następuje po potwierdzeniu

Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageName != null) {
   newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
}
startActivity(newIntent);
Alkersan
źródło
33
Android.permission.INSTALL_PACKAGES jest dostępny tylko dla aplikacji podpisanych przez system. Tak więc to niewiele by pomogło
Hubert Liberacki
21

Chcę tylko podzielić się faktem, że mój plik apk został zapisany w katalogu „Data” w mojej aplikacji i że muszę zmienić uprawnienia do pliku apk, aby był czytelny na całym świecie, aby umożliwić jego instalację w ten sposób, w przeciwnym razie system rzucał „Błąd analizy: Wystąpił problem podczas analizowania pakietu”; więc używając rozwiązania @Horaceman, które sprawia:

File file = new File(dir, "App.apk");
file.setReadable(true, false);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
użytkownik1604240
źródło
Otrzymuję ten sam błąd analizatora składni! Nie wiem jak to rozwiązać? Ustawiam file.setReadable (prawda, fałsz), ale to nie działa dla mnie
Jai
15

To może bardzo pomóc innym!

Pierwszy:

private static final String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyAppFolderInStorage/";

private void install() {
    File file = new File(APP_DIR + fileName);

    if (file.exists()) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        String type = "application/vnd.android.package-archive";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri downloadedApk = FileProvider.getUriForFile(getContext(), "ir.greencode", file);
            intent.setDataAndType(downloadedApk, type);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.fromFile(file), type);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        getContext().startActivity(intent);
    } else {
        Toast.makeText(getContext(), "ّFile not found!", Toast.LENGTH_SHORT).show();
    }
}

Po drugie: w przypadku systemu Android 7 i nowszych należy zdefiniować dostawcę w manifeście, jak poniżej!

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="ir.greencode"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
    </provider>

Po trzecie: Zdefiniuj path.xml w folderze res / xml jak poniżej! Używam tej ścieżki do pamięci wewnętrznej, jeśli chcesz zmienić ją na coś innego, istnieje kilka sposobów! Możesz przejść do tego linku: FileProvider

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="your_folder_name" path="MyAppFolderInStorage/"/>
</paths>

Po czwarte: Powinieneś dodać to zezwolenie w manifeście:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

Zezwala aplikacji na żądanie instalacji pakietów. Aplikacje kierowane na interfejsy API większe niż 25 muszą posiadać to uprawnienie, aby móc korzystać z Intent.ACTION_INSTALL_PACKAGE.

Upewnij się, że władze dostawcy są takie same!


Uwaga Hadi
źródło
3
Dzięki, czwarty krok był tym, czego brakowało w nich wszystkich.
Amin Keshavarzian,
1
Rzeczywiście, czwarty punkt jest absolutnie konieczny. Wszystkie inne odpowiedzi całkowicie tego przeoczyły.
whiteagle
Czy Android.permission.REQUEST_INSTALL_PACKAGES jest dostępny tylko dla aplikacji systemowych lub aplikacji podpisanych przy użyciu systemowego magazynu kluczy lub cokolwiek innego?
pavi2410,
@Pavitra Zezwala aplikacji na żądanie instalowania pakietów. Aplikacje kierowane na interfejsy API większe niż 25 muszą posiadać to uprawnienie, aby móc korzystać z Intent.ACTION_INSTALL_PACKAGE. Poziom ochrony: podpis
Hadi Note
Ten kod nie używa zamiaru.ACTION_INSTALL_PACKAGE. Skąd więc to zezwolenie?
Alexander Dyagilev
5

Inne rozwiązanie, które nie wymaga kodowania aplikacji odbierającej na stałe, a zatem jest bezpieczniejsze:

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData( Uri.fromFile(new File(pathToApk)) );
startActivity(intent);
Hartok
źródło
4

Tak, to możliwe. Ale do tego potrzebujesz telefonu, aby zainstalować niezweryfikowane źródła. Na przykład SlideMe to robi. Myślę, że najlepszą rzeczą, jaką możesz zrobić, to sprawdzić, czy aplikacja jest obecna i wysłać zamiar dla Android Market. powinieneś użyć czegoś w schemacie URL dla Android Market.

market://details?id=package.name

Nie wiem dokładnie, jak rozpocząć działanie, ale jeśli rozpoczniesz działanie z tego rodzaju adresem URL. Powinien otworzyć rynek Android Market i dać ci możliwość zainstalowania aplikacji.

Loïc Faure-Lacroix
źródło
Jak widzę, to rozwiązanie jest najbliższe prawdy :). Ale to nie jest właściwe w moim przypadku. Potrzebuję dynamicznego modułu ładującego, zależnego od bieżącego środowiska użytkownika i wchodzenia na rynek - niezbyt dobre rozwiązanie. Ale i tak dziękuję.
Alkersan
4

Warto zauważyć, że jeśli używasz DownloadManagerdo rozpoczęcia pobierania, pamiętaj, aby zapisać go w zewnętrznej lokalizacji, np setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. Intencja z typem pakietu archiwum nie wydaje się podobać do content:schematu używanego do pobierania do wewnętrznej lokalizacji, ale się podoba file:. (Próba zawinięcia wewnętrznej ścieżki w obiekt File, a następnie uzyskanie ścieżki również nie działa, nawet jeśli powoduje powstanie file:adresu URL, ponieważ aplikacja nie analizuje pliku APK; wygląda na to, że musi być zewnętrzna.)

Przykład:

int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String downloadedPackageUriString = cursor.getString(uriIndex);
File mFile = new File(Uri.parse(downloadedPackageUriString).getPath());
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
        .setDataAndType(Uri.fromFile(mFile), "application/vnd.android.package-archive")
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(promptInstall);
qix
źródło
3

Nie zapomnij poprosić o uprawnienia:

android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
android.Manifest.permission.READ_EXTERNAL_STORAGE

Dodaj w AndroidManifest.xml dostawcę i pozwolenie:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
...
<application>
    ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
</application>

Utwórz dostawcę plików XML res / xml / provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="external"
        path="." />
    <external-files-path
        name="external_files"
        path="." />
    <cache-path
        name="cache"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
    <files-path
        name="files"
        path="." />
</paths>

Użyj poniższego przykładowego kodu:

   public class InstallManagerApk extends AppCompatActivity {

    static final String NAME_APK_FILE = "some.apk";
    public static final int REQUEST_INSTALL = 0;

     @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // required permission:
        // android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
        // android.Manifest.permission.READ_EXTERNAL_STORAGE

        installApk();

    }

    ...

    /**
     * Install APK File
     */
    private void installApk() {

        try {

            File filePath = Environment.getExternalStorageDirectory();// path to file apk
            File file = new File(filePath, LoadManagerApkFile.NAME_APK_FILE);

            Uri uri = getApkUri( file.getPath() ); // get Uri for  each SDK Android

            Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
            intent.setData( uri );
            intent.setFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK );
            intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
            intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
            intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, getApplicationInfo().packageName);

            if ( getPackageManager().queryIntentActivities(intent, 0 ) != null ) {// checked on start Activity

                startActivityForResult(intent, REQUEST_INSTALL);

            } else {
                throw new Exception("don`t start Activity.");
            }

        } catch ( Exception e ) {

            Log.i(TAG + ":InstallApk", "Failed installl APK file", e);
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG)
                .show();

        }

    }

    /**
     * Returns a Uri pointing to the APK to install.
     */
    private Uri getApkUri(String path) {

        // Before N, a MODE_WORLD_READABLE file could be passed via the ACTION_INSTALL_PACKAGE
        // Intent. Since N, MODE_WORLD_READABLE files are forbidden, and a FileProvider is
        // recommended.
        boolean useFileProvider = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;

        String tempFilename = "tmp.apk";
        byte[] buffer = new byte[16384];
        int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;
        try (InputStream is = new FileInputStream(new File(path));
             FileOutputStream fout = openFileOutput(tempFilename, fileMode)) {

            int n;
            while ((n = is.read(buffer)) >= 0) {
                fout.write(buffer, 0, n);
            }

        } catch (IOException e) {
            Log.i(TAG + ":getApkUri", "Failed to write temporary APK file", e);
        }

        if (useFileProvider) {

            File toInstall = new File(this.getFilesDir(), tempFilename);
            return FileProvider.getUriForFile(this,  BuildConfig.APPLICATION_ID, toInstall);

        } else {

            return Uri.fromFile(getFileStreamPath(tempFilename));

        }

    }

    /**
     * Listener event on installation APK file
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == REQUEST_INSTALL) {

            if (resultCode == Activity.RESULT_OK) {
                Toast.makeText(this,"Install succeeded!", Toast.LENGTH_SHORT).show();
            } else if (resultCode == Activity.RESULT_CANCELED) {
                Toast.makeText(this,"Install canceled!", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this,"Install Failed!", Toast.LENGTH_SHORT).show();
            }

        }

    }

    ...

}
amiron
źródło
2

Tylko rozszerzenie, jeśli ktoś potrzebuje biblioteki, może to pomóc. Dzięki Raghav

IronBlossom
źródło
2

Spróbuj tego

String filePath = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
String title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
MainActivity.this.startActivity(intent);
Joolah
źródło
1

Najpierw dodaj następujący wiersz do pliku AndroidManifest.xml:

<uses-permission android:name="android.permission.INSTALL_PACKAGES"
    tools:ignore="ProtectedPermissions" />

Następnie użyj następującego kodu, aby zainstalować apk:

File sdCard = Environment.getExternalStorageDirectory();
            String fileStr = sdCard.getAbsolutePath() + "/MyApp";// + "app-release.apk";
            File file = new File(fileStr, "TaghvimShamsi.apk");
            Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                    "application/vnd.android.package-archive");
            startActivity(promptInstall);
Abbas.moosavi
źródło
0

UpdateNode zapewnia interfejs API dla Androida do instalowania pakietów APK z innej aplikacji.

Możesz po prostu zdefiniować swoją aktualizację online i zintegrować interfejs API z aplikacją - to wszystko.
Obecnie interfejs API jest w wersji Beta, ale możesz już samodzielnie wykonać kilka testów.

Poza tym UpdateNode oferuje również wyświetlanie komunikatów przez system - całkiem przydatne, jeśli chcesz powiedzieć coś ważnego dla swoich użytkowników.

Należę do zespołu programistów klienta i używam przynajmniej funkcji wiadomości w mojej własnej aplikacji na Androida.

Zobacz tutaj opis integracji API

Sarahara
źródło
są pewne problemy ze stroną internetową. Nie mogę uzyskać api_key i nie mogę kontynuować rejestracji.
infinite_loop_
Cześć @infinite_loop_ klucz API można znaleźć w sekcji konta użytkownika: updatenode.com/profile/view_keys
sarahara
0

Spróbuj tego - napisz na Manifeście:

uses-permission android:name="android.permission.INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions"

Napisz kod:

File sdCard = Environment.getExternalStorageDirectory();
String fileStr = sdCard.getAbsolutePath() + "/Download";// + "app-release.apk";
File file = new File(fileStr, "app-release.apk");
Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                        "application/vnd.android.package-archive");

startActivity(promptInstall);
Phuoc-Loc Truong
źródło
1
Nie potrzebujesz tego uprawnienia tylko do systemu, aby uruchomić działanie Instalatora pakietów
Zegar