How To APK Install Programmatically
targetSdkVersion을 24이상부터 file 접근하는 방법이 변경됨
[Android 7.0 변경사항 참고 : https://developer.android.com/about/versions/nougat/android-7.0-changes?hl=ko]
[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를 이용한 코드는 다음에...
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 |