본문 바로가기

Programming/Android

[Android] How To APK Install Programmatically

How To APK Install Programmatically

targetSdkVersion을 24이상부터 file 접근하는 방법이 변경됨

[Code]

AndroidManifest.xml

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

    ...

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="kr.co.foodfly.viewtest.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">

    <meta-data
      android:name="android.support.FILE_PROVIDER_PATHS"
      android:resource="@xml/file_provider"/>
</provider>
    ...

  

res/xml/file_provider.xml

123456<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
    name="external_files"
    path="apk" />
</paths>
  

[Function] install

12345678910111213141516171819private void install(Context context) {
    Observable installApk = update(context, APP_URL);
    if (installApk != null) {
        Snackbar.make(mProgress.getRootView(), "start apk install", Snackbar.LENGTH_SHORT).show();
        mViewDisposable.add(
                installApk.subscribe(new Consumer() {
                    @Override
                    public void accept(Intent install) throws Exception {
                        startActivity(install);
                        finish();
                    }
                }, new Consumer() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        throwable.printStackTrace();
                    }
                }));
    }
}

[Function] update

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364private Observable update(final Context context, final String url) {
    if (context == null || TextUtils.isEmpty(url) || !checkPermission((Activity) context)) return null;
    return Observable.create(new ObservableOnSubscribe() {
        @Override
        public void subscribe(ObservableEmitter observer) throws Exception {
            HttpURLConnection urlConnection = (HttpURLConnection) (new URL(url).openConnection());
            urlConnection.setRequestMethod("GET");
            urlConnection.connect();
            String filePath = Environment.getExternalStorageDirectory().getPath() + "/apk/";
            File file = new File(filePath);
            if (!file.exists()) {
                try {
                    boolean isSuccess = file.mkdirs();
                    if (!isSuccess) {
                        Log.i("Create File", "Failed");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            File outputFile = new File(file, "test.apk");
            FileOutputStream outputStream = new FileOutputStream(outputFile);
            InputStream inputStream = urlConnection.getInputStream();
            byte[] buffer = new byte[1024];
            int lengthOfFile = urlConnection.getContentLength();
            int read = 0;
            int total = 0;
            mProgress.show();
            while ((read = inputStream.read(buffer)) != -1) {
                total += read;
                outputStream.write(buffer, 0, read);
                // show progress loading
                mProgress.setProgress(total * 100 / lengthOfFile);
            }
            outputStream.close();
            inputStream.close();
            observer.onNext(outputFile);
            observer.onComplete();
        }
    }).subscribeOn(Schedulers.io()).doOnSubscribe(new Consumer() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            // dismiss progress dialog
            mProgress.hide();
        }
    }).map(new Function() {
        @Override
        public Intent apply(File file) throws Exception {
            Intent intent;
            Uri contentUri;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                contentUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", file);
                intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            } else {
                contentUri = Uri.fromFile(file);
                intent = new Intent(Intent.ACTION_VIEW);
            }
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            return intent;
        }
    });
}

[Function] chekcPermission(Read/Write)

1234567891011121314151617181920212223242526private boolean checkPermission(Activity activity) {
    // Write/Read Permission
    if (ContextCompat.checkSelfPermission(activity, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(activity, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        String[] permission = new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE }; |
        ActivityCompat.requestPermissions(activity, permission, REQUEST_PERMISSION);
        return false;
    }
    return true;
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_PERMISSION) {
        for(int result : grantResults) {
            if(result != PackageManager.PERMISSION_GRANTED) {
                Snackbar.make(mProgress.getRootView(), "모든 권한이 설정되어야 앱을 사용하실 수 있습니다.", Snackbar.LENGTH_SHORT).show();
                return;
            }
        }
        install(this);
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

[Full Code]

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v4.widget.ContentLoadingProgressBar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

/**
 * Created by hyogeun.park on 2017. 11. 22..
 */

public class BlogActivity extends AppCompatActivity {

    private final static int REQUEST_PERMISSION = BlogActivity.class.hashCode() & 0x000000ff;
    private final static String APP_URL = "";

    private CompositeDisposable mViewDisposable = new CompositeDisposable();
    private ContentLoadingProgressBar mProgress;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_blog);
        mProgress = findViewById(R.id.progress_bar);
        findViewById(R.id.test_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                install(v.getContext());
            }
        });
    }

    private void install(Context context) {
        Observable installApk = update(context, APP_URL);
        if (installApk != null) {
            Snackbar.make(mProgress.getRootView(), "start apk install", Snackbar.LENGTH_SHORT).show();
            mViewDisposable.add(
                    installApk.subscribe(new Consumer() {
                        @Override
                        public void accept(Intent install) throws Exception {
                            startActivity(install);
                            finish();
                        }
                    }, new Consumer() {
                        @Override
                        public void accept(Throwable throwable) throws Exception {
                            throwable.printStackTrace();
                        }
                    }));
        }
    }

    private Observable update(final Context context, final String url) {
        if (context == null || TextUtils.isEmpty(url) || !checkPermission((Activity) context))
            return null;
        return Observable.create(new ObservableOnSubscribe() {
            @Override
            public void subscribe(ObservableEmitter observer) throws Exception {
                HttpURLConnection urlConnection = (HttpURLConnection) (new URL(url).openConnection());
                urlConnection.setRequestMethod("GET");
                urlConnection.connect();

                String filePath = Environment.getExternalStorageDirectory().getPath() + "/apk/";
                File file = new File(filePath);
                if (!file.exists()) {
                    try {
                        boolean isSuccess = file.mkdirs();
                        if (!isSuccess) {
                            Log.i("Create File", "Failed");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                File outputFile = new File(file, "test.apk");
                FileOutputStream outputStream = new FileOutputStream(outputFile);
                InputStream inputStream = urlConnection.getInputStream();

                byte[] buffer = new byte[1024];
                int lengthOfFile = urlConnection.getContentLength();
                int read = 0;
                int total = 0;
                mProgress.show();
                while ((read = inputStream.read(buffer)) != -1) {
                    total += read;
                    outputStream.write(buffer, 0, read);
                    // show progress loading
                    mProgress.setProgress(total * 100 / lengthOfFile);
                }
                outputStream.close();
                inputStream.close();

                observer.onNext(outputFile);
                observer.onComplete();
            }
        }).subscribeOn(Schedulers.io()).doOnSubscribe(new Consumer() {
            @Override
            public void accept(Disposable disposable) throws Exception {
                // dismiss progress dialog
                mProgress.hide();
            }
        }).map(new Function() {
            @Override
            public Intent apply(File file) throws Exception {
                Intent intent;
                Uri contentUri;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    contentUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", file);
                    intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                } else {
                    contentUri = Uri.fromFile(file);
                    intent = new Intent(Intent.ACTION_VIEW);
                }
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
                return intent;
            }
        });
    }

    private boolean checkPermission(Activity activity) {
        // Write/Read Permission
        if (ContextCompat.checkSelfPermission(activity, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(activity, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            String[] permission = new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE}; |
            ActivityCompat.requestPermissions(activity, permission, REQUEST_PERMISSION);
            return false;
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_PERMISSION) {
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    Snackbar.make(mProgress.getRootView(), "모든 권한이 설정되어야 앱을 사용하실 수 있습니다.", Snackbar.LENGTH_SHORT).show();
                    return;
                }
            }
            install(this);
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        mViewDisposable.clear();
    }
}

[Result]

DownloadManager를 이용한 코드는 다음에...

[Reference]

[Android] FileProvider :: android.os.FileUriExposedException - 항상 초심으로
[팁] FileUriExposedException : 안드로이드 카메라 인텐트 에러 :: KYOME

'Programming > Android' 카테고리의 다른 글

[Android] Context  (0) 2018.09.18
[Android] 앱 구성 요소(Application Component)  (2) 2018.09.15
[Android] Input Event  (0) 2018.06.16
[Android] View  (0) 2018.06.14
[Android] Dependency Structure  (0) 2018.04.19