Android에 프로그래밍 방식으로 응용 프로그램 설치


211

사용자 정의 Android 애플리케이션에서 동적으로 다운로드 한 APK를 프로그래밍 방식으로 설치할 수 있는지 알고 싶습니다.


"현재 사용자 환경에 따른 동적 로더"의 의미가 무엇인지 모르겠습니다. @Lie Ryan이 제공 한 답변은 원하는 방법으로 다운로드 한 APK를 설치하는 방법을 보여줍니다.
CommonsWare

답변:


240

Play 스토어 링크 또는 설치 프롬프트를 쉽게 시작할 수 있습니다.

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

또는

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

그러나 사용자의 명시 적 허가 없이는 .apks를 설치할 수 없습니다 . 장치와 프로그램이 루팅되지 않는 한 아닙니다.


35
좋은 답변이지만 하드 코딩하지 마십시오 /sdcard.Android 2.2 이상 및 기타 기기에서는 잘못 되었기 때문입니다. Environment.getExternalStorageDirectory()대신 사용하십시오 .
CommonsWare

3
/ asset / 디렉토리는 개발 머신에만 존재하며, 응용 프로그램이 APK로 컴파일 될 때 모든 자산이 APK 안에 압축되어 있기 때문에 / asset / 디렉토리는 더 이상 존재하지 않습니다. / asset / 디렉토리에서 설치하려면 먼저 다른 폴더로 추출해야합니다.
Lie Ryan

2
@LieRyan. 당신의 답변을 만나서 반갑습니다. 사용자 지정 ROM이있는 루팅 된 장치가 있습니다. 사용자에게 설치 버튼을 누르지 않고 테마를 동적으로 설치하고 싶습니다. 내가 할 수 있을까
Sharanabasu Angadi

1
targetSdk가 25 일 때 더 이상 작동하지 않는 것 같습니다. "android.os.FileUriExposedException : ... apk는 Intent.getData () ...를 통해 앱 이외의 노출됨"예외를 제공합니다. 어떻게 오세요?
안드로이드 개발자

4
@ 안드로이드 개발자 : 현재 이것을 테스트 할 수는 없지만 targetSdkVersion> = 24에서는 다음이 적용됩니다. stackoverflow.com/questions/38200282/… FileProvider를 사용해야합니다.
거짓말 라이언

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);

나는 같은 문제가 있었고 여러 번 시도한 후에 이런 식으로 나에게 도움이되었습니다. 이유를 모르겠지만 데이터와 유형을 별도로 설정하면 의도가 악화되었습니다.


이 답변을 충분히지지 할 수 없습니다. 어떤 이유로 인 텐트 데이터와 MIME 유형을 별도로 설정하면 API 레벨 17에서 ActivityNotFoundException이 발생합니다.
Brent M. Spell

나는 방금 이것도 투표했다. 이 양식은 내가 일할 수있는 유일한 friggin 양식이었습니다. 몇 년 후 ..이 괴물 버그는 무엇입니까? 시간 낭비. BTW Eclipse (Helios)를 사용하고 있습니다.
Tam

10
@ BrentM.Spell 및 기타 : 문서를 살펴보면 데이터 또는 유형 만 설정할 때마다 다른 하나는 자동으로 무효화됩니다 (예 : setData()유형 매개 변수가 제거됨). setDataAndType()둘 다에 값을 제공하려면 반드시 사용해야 합니다. 여기 : developer.android.com/reference/android/content/…
Bogdan Alexandru

41

이 질문에 제공된 해결책은 모두 targetSdkVersion23 이하에 적용 됩니다. 그러나 Android N, 즉 API 레벨 24 이상에서는 다음 예외와 함께 작동하지 않으며 충돌합니다.

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

이것은 Android 24부터 Uri다운로드 파일의 주소 지정이 변경 되었기 때문 입니다. 예를 들어 appName.apk패키지 이름을 가진 앱의 기본 외부 파일 시스템에 저장된 설치 파일 com.example.test은 다음과 같습니다.

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

API 23, 아래 같은 반면,

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

대한 API 24이상.

이것에 대한 자세한 내용은 여기 에서 찾을 수 있으며 나는 그것을 거치지 않을 것입니다.

에 대한 질문에 대답하기 targetSdkVersion24이상, 하나는 다음 단계를 수행하는 다음의 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. src, main paths.xmlxml폴더에 다음 파일을 추가하십시오 res.

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

이는 pathName위의 예시적인 컨텐츠 uri 예 pathValue에 표시된 것이며 시스템의 실제 경로입니다. "."를 넣는 것이 좋습니다. 추가 서브 디렉토리를 추가하지 않으려는 경우 위의 pathValue에 대해 (따옴표없이).

  1. appName.apk기본 외부 파일 시스템에 이름 으로 apk를 설치하려면 다음 코드를 작성하십시오 .

    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();

외부 파일 시스템에서 자신의 앱의 개인 디렉토리에 쓸 때도 권한이 필요하지 않습니다.

나는 자동 업데이트 라이브러리를 작성했습니다 여기에 내가 위의 내용을 사용하고있는.


2
나는이 방법을 따랐다. 그러나 설치 버튼을 누르면 파일이 손상되었다고 말합니다. 블루투스를 통해 전송하여 동일한 파일을 설치할 수 없습니다. 왜 그래?

HI 파일 손상 오류가 발생하면 APK를 가져 오기 위해 앱에 제공 한 전송 모드는 무엇입니까? 서버에서 다운로드하는 경우 서버에서 복사 파일의 스트림을 플러시 하시겠습니까? 블루투스 작동으로 전송 된 APK를 설치하기 때문에 그것이 문제라고 생각합니다.
Sridhar S

2
java.lang.NullPointerException이는 : 널 객체 참조 가상 메소드 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData (android.content.pm.PackageManager, java.lang.String의)를 "호출하려고
Makvin

귀하의 라이브러리는 사용법에 대한 간단한 지침으로 마지막에 간단한 readme를 가질 수 있습니다 ...;)
Renetik

2
이 답변에 대한 일주일의 투쟁 끝에 나는 내 문제를 해결했습니다. @SridharS, 나는 내가 보는대로, 오래 전이었다 알고 있지만, 관심, 5 행에 추가하는 경우는 .authorityStrcontext.getPackageName()다음 작업을해야합니다.
sehrob

30

글쎄, 나는 더 깊이 파고 안드로이드 소스에서 PackageInstaller 응용 프로그램의 소스를 찾았습니다.

https://github.com/android/platform_packages_apps_packageinstaller

매니페스트에서 권한이 필요하다는 것을 알았습니다.

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

그리고 실제 설치 과정은 확인 후 발생합니다.

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);

33
android.permission.INSTALL_PACKAGES는 시스템 서명 앱에서만 사용할 수 있습니다. 이 많은 도움이되지 것입니다 그래서
휴 버트 Liberacki

21

내 apk 파일이 내 앱 "Data"디렉토리에 저장되었고 apk 파일에 대한 권한을 세상에 읽을 수 있도록 변경하여 그와 같은 방식으로 설치해야한다는 사실을 공유하고 싶습니다. "구문 분석 오류 : 패키지를 구문 분석하는 중에 문제점이 발생했습니다"; @Horaceman의 솔루션을 사용하여 다음을 수행하십시오.

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);

동일한 파서 오류가 발생합니다! 나는 그것을 해결하는 방법을 모른다? file.setReadable (true, false)을 설정했지만 작동하지 않습니다.
Jai

15

이것은 다른 사람들을 많이 도울 수 있습니다!

먼저:

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();
    }
}

둘째 : 안드로이드 7 이상에서는 아래와 같이 매니페스트에 공급자를 정의해야합니다!

    <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>

셋째 : 아래와 같이 res / xml 폴더에 path.xml을 정의하십시오! 다른 방법으로 변경하려면 내부 스토리지 에이 경로 를 사용 하고 있습니다. 이 링크로 이동할 수 있습니다 : 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>

넷째 : 이 권한을 매니페스트에 추가해야합니다.

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

애플리케이션이 패키지 설치를 요청할 수 있도록합니다. Intent.ACTION_INSTALL_PACKAGE를 사용하려면 25보다 큰 API를 대상으로하는 앱이이 권한을 보유해야합니다.

공급 업체 권한이 동일한 지 확인하십시오!



3
고마워, 네 번째 단계는 그들 모두에서 빠진 것이 었습니다.
Amin Keshavarzian

1
실제로, 4 점은 절대적으로 필요합니다. 다른 모든 답변은 그것을 완전히 놓쳤다.
whiteagle

android.permission.REQUEST_INSTALL_PACKAGES는 시스템 키 저장소로 서명 된 시스템 앱 또는 앱에만 사용할 수 있습니까?
pavi2410

@Pavitra 응용 프로그램이 패키지 설치를 요청할 수 있도록합니다. Intent.ACTION_INSTALL_PACKAGE를 사용하려면 25보다 큰 API를 대상으로하는 앱이이 권한을 보유해야합니다. 보호 수준 : 서명
Hadi Note

이 코드는 Intent.ACTION_INSTALL_PACKAGE를 사용하지 않습니다. 왜이 권한이 있습니까 ??
Alexander Dyagilev

5

수신 앱을 하드 코딩 할 필요가 없으므로 더 안전한 다른 솔루션 :

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData( Uri.fromFile(new File(pathToApk)) );
startActivity(intent);

4

네 가능합니다. 그러나 확인되지 않은 소스를 설치하려면 전화가 필요합니다. 예를 들어 slideMe가 그렇게합니다. 나는 당신이 할 수있는 가장 좋은 방법은 응용 프로그램이 있는지 확인하고 Android 마켓에 대한 의도를 보내는 것입니다. 당신은 안드로이드 마켓에 대한 URL 체계를 사용해야합니다.

market://details?id=package.name

활동을 시작하는 방법을 정확히 모르지만 해당 URL로 활동을 시작하는 경우. 안드로이드 마켓을 열고 앱을 설치할 수있는 옵션을 제공해야합니다.


내가 알다시피,이 솔루션은 진실에 가장 가깝습니다 :). 그러나 내 경우에는 적합하지 않습니다. 현재 사용자 환경에 의존하고 시장에 나가는 동적 로더가 필요합니다. 좋은 솔루션은 아닙니다. 어쨌든 고마워
Alkersan

4

를 사용 DownloadManager하여 다운로드를 시작하는 경우 외부 위치 (예 :)에 저장해야합니다 setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. 패키지 아카이브 유형의 의도 content:는 내부 위치로 다운로드하는 데 사용되는 체계 를 좋아하지 않는 것처럼 보이지만 좋아 file:합니다. ( file:앱이 apk를 구문 분석하지 않기 때문에 URL을 생성하더라도 내부 경로를 File 객체로 래핑 한 다음 경로를 가져 오는 것도 작동하지 않습니다 . 외부이어야합니다.)

예:

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);

3

권한을 요청하는 것을 잊지 마십시오 :

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

AndroidManifest.xml에 제공자 및 권한을 추가하십시오.

<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>

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>

아래 예제 코드를 사용하십시오.

   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();
            }

        }

    }

    ...

}

2

누군가가 라이브러리가 필요하면 확장 기능 만 있으면 도움 될 수 있습니다. Raghav 덕분에


2

이 시도

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);

1

먼저 AndroidManifest.xml에 다음 행을 추가하십시오.

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

그런 다음 다음 코드를 사용하여 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);

0

UpdateNode 는 다른 앱 내부에서 APK 패키지를 설치하기위한 Android 용 API를 제공합니다.

온라인으로 업데이트를 정의하고 API를 앱에 통합하면됩니다.
현재 API는 베타 상태이지만 이미 일부 테스트를 직접 수행 할 수 있습니다.

그 외에도 UpdateNode는 시스템을 통해 메시지를 표시합니다. 사용자에게 중요한 정보를 말하려는 경우 매우 유용합니다.

나는 클라이언트 개발 팀의 일원이며 적어도 내 안드로이드 앱에 메시지 기능을 사용하고 있습니다.

API 통합 방법에 대한 설명은 여기를 참조하십시오.


웹 사이트에 문제가 있습니다. api_key를 얻을 수 없으며 등록을 계속할 수 없습니다.
infinite_loop_

@infinite_loop_ 님, 사용자 계정 섹션에서 API 키를 찾을 수 있습니다. updatenode.com/profile/view_keys
sarahara

0

이것을 시도하십시오-매니페스트에 쓰기 :

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

코드를 작성하십시오 :

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);

1
패키지 설치 프로그램 활동을 시작하기 위해 시스템 전용 권한이 필요하지 않습니다
Clocker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.