1
0
Code Issues Pull Requests Packages Projects Releases Wiki Activity GitHub Gitee

Implement download and install apk

This commit is contained in:
sunnylqm 2020-09-27 22:16:27 +08:00
parent 3c5792423e
commit 17ff366f51
10 changed files with 176 additions and 42 deletions

View File

@ -21,6 +21,7 @@ import {
switchVersion, switchVersion,
switchVersionLater, switchVersionLater,
markSuccess, markSuccess,
downloadAndInstallApk,
} from 'react-native-update'; } from 'react-native-update';
import _updateConfig from '../update.json'; import _updateConfig from '../update.json';
@ -94,11 +95,29 @@ export default class App extends Component {
return; return;
} }
if (info.expired) { if (info.expired) {
Alert.alert('提示', '您的应用版本已更新,请前往应用商店下载新的版本', [ Alert.alert('提示', '您的应用版本已更新,点击确定下载安装新版本', [
{ {
text: '确定', text: '确定',
onPress: () => { onPress: () => {
info.downloadUrl && Linking.openURL(info.downloadUrl); if (info.downloadUrl) {
// apk可直接下载安装
if (
Platform.OS === 'android' &&
info.downloadUrl.endsWith('.apk')
) {
downloadAndInstallApk({
url: info.downloadUrl,
onDownloadProgress: ({received, total}) => {
this.setState({
received,
total,
});
},
});
} else {
Linking.openURL(info.downloadUrl);
}
}
}, },
}, },
]); ]);

View File

@ -1,7 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.reactnative.modules.update"> package="cn.reactnative.modules.update">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application> <application>
<meta-data android:name="pushy_build_time" android:value="@string/pushy_build_time" /> <meta-data android:name="pushy_build_time" android:value="@string/pushy_build_time" />
<provider
android:name=".PushyFileProvider"
android:authorities="${applicationId}.pushy.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>

View File

@ -72,7 +72,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
private void downloadFile(DownloadTaskParams param) throws IOException { private void downloadFile(DownloadTaskParams param) throws IOException {
String url = param.url; String url = param.url;
File writePath = param.zipFilePath; File writePath = param.targetFile;
this.hash = param.hash; this.hash = param.hash;
OkHttpClient client = new OkHttpClient(); OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url) Request request = new Request.Builder().url(url)
@ -243,10 +243,10 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
copyFilesWithBlacklist("", from, to, blackList); copyFilesWithBlacklist("", from, to, blackList);
} }
private void doDownload(DownloadTaskParams param) throws IOException { private void doFullPatch(DownloadTaskParams param) throws IOException {
downloadFile(param); downloadFile(param);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath))); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.targetFile)));
ZipEntry ze; ZipEntry ze;
String filename; String filename;
@ -303,7 +303,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONException { private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONException {
downloadFile(param); downloadFile(param);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath))); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.targetFile)));
ZipEntry ze; ZipEntry ze;
int count; int count;
String filename; String filename;
@ -379,7 +379,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
private void doPatchFromPpk(DownloadTaskParams param) throws IOException, JSONException { private void doPatchFromPpk(DownloadTaskParams param) throws IOException, JSONException {
downloadFile(param); downloadFile(param);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath))); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.targetFile)));
ZipEntry ze; ZipEntry ze;
int count; int count;
String filename; String filename;
@ -464,8 +464,8 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
protected Void doInBackground(DownloadTaskParams... params) { protected Void doInBackground(DownloadTaskParams... params) {
try { try {
switch (params[0].type) { switch (params[0].type) {
case DownloadTaskParams.TASK_TYPE_FULL_DOWNLOAD: case DownloadTaskParams.TASK_TYPE_PATCH_FULL:
doDownload(params[0]); doFullPatch(params[0]);
break; break;
case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK: case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK:
doPatchFromApk(params[0]); doPatchFromApk(params[0]);
@ -473,11 +473,18 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK: case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK:
doPatchFromPpk(params[0]); doPatchFromPpk(params[0]);
break; break;
case DownloadTaskParams.TASK_TYPE_CLEARUP: case DownloadTaskParams.TASK_TYPE_CLEANUP:
doCleanUp(params[0]); doCleanUp(params[0]);
break; break;
case DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD:
downloadFile(params[0]);
break;
default:
break;
}
if (params[0].listener != null) {
params[0].listener.onDownloadCompleted(params[0]);
} }
params[0].listener.onDownloadCompleted();
} catch (Throwable e) { } catch (Throwable e) {
if (UpdateContext.DEBUG) { if (UpdateContext.DEBUG) {
e.printStackTrace(); e.printStackTrace();

View File

@ -8,17 +8,18 @@ import java.io.File;
* Created by tdzl2003 on 3/31/16. * Created by tdzl2003 on 3/31/16.
*/ */
class DownloadTaskParams { class DownloadTaskParams {
static final int TASK_TYPE_FULL_DOWNLOAD = 1; static final int TASK_TYPE_PATCH_FULL = 1;
static final int TASK_TYPE_PATCH_FROM_APK = 2; static final int TASK_TYPE_PATCH_FROM_APK = 2;
static final int TASK_TYPE_PATCH_FROM_PPK = 3; static final int TASK_TYPE_PATCH_FROM_PPK = 3;
static final int TASK_TYPE_PLAIN_DOWNLOAD = 4;
static final int TASK_TYPE_CLEARUP = 0; //Keep hash & originHash static final int TASK_TYPE_CLEANUP = 0; //Keep hash & originHash
int type; int type;
String url; String url;
String hash; String hash;
String originHash; String originHash;
File zipFilePath; File targetFile;
File unzipDirectory; File unzipDirectory;
File originDirectory; File originDirectory;
UpdateContext.DownloadFileListener listener; UpdateContext.DownloadFileListener listener;

View File

@ -0,0 +1,14 @@
package cn.reactnative.modules.update;
import android.support.v4.content.FileProvider;
/**
* Providing a custom {@code FileProvider} prevents manifest {@code <provider>} name collisions.
*
* See https://developer.android.com/guide/topics/manifest/provider-element.html for details.
*/
public class PushyFileProvider extends FileProvider {
// This class intentionally left blank.
}

View File

@ -46,7 +46,7 @@ public class UpdateContext {
editor.putString("packageVersion", packageVersion); editor.putString("packageVersion", packageVersion);
editor.apply(); editor.apply();
this.clearUp(); this.cleanUp();
} }
} }
@ -86,28 +86,39 @@ public class UpdateContext {
} }
public interface DownloadFileListener { public interface DownloadFileListener {
void onDownloadCompleted(); void onDownloadCompleted(DownloadTaskParams params);
void onDownloadFailed(Throwable error); void onDownloadFailed(Throwable error);
} }
public void downloadFile(String url, String hash, DownloadFileListener listener) { public void downloadFullUpdate(String url, String hash, DownloadFileListener listener) {
DownloadTaskParams params = new DownloadTaskParams(); DownloadTaskParams params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_FULL_DOWNLOAD; params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL;
params.url = url; params.url = url;
params.hash = hash; params.hash = hash;
params.listener = listener; params.listener = listener;
params.zipFilePath = new File(rootDir, hash + ".ppk"); params.targetFile = new File(rootDir, hash + ".ppk");
params.unzipDirectory = new File(rootDir, hash); params.unzipDirectory = new File(rootDir, hash);
new DownloadTask(context).executeOnExecutor(this.executor, params); new DownloadTask(context).executeOnExecutor(this.executor, params);
} }
public void downloadFile(String url, String hash, String fileName, DownloadFileListener listener) {
DownloadTaskParams params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = new File(rootDir, fileName);
// params.unzipDirectory = new File(rootDir, hash);
new DownloadTask(context).executeOnExecutor(this.executor, params);
}
public void downloadPatchFromApk(String url, String hash, DownloadFileListener listener) { public void downloadPatchFromApk(String url, String hash, DownloadFileListener listener) {
DownloadTaskParams params = new DownloadTaskParams(); DownloadTaskParams params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK; params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK;
params.url = url; params.url = url;
params.hash = hash; params.hash = hash;
params.listener = listener; params.listener = listener;
params.zipFilePath = new File(rootDir, hash + ".apk.patch"); params.targetFile = new File(rootDir, hash + ".apk.patch");
params.unzipDirectory = new File(rootDir, hash); params.unzipDirectory = new File(rootDir, hash);
new DownloadTask(context).executeOnExecutor(this.executor, params); new DownloadTask(context).executeOnExecutor(this.executor, params);
} }
@ -119,7 +130,7 @@ public class UpdateContext {
params.hash = hash; params.hash = hash;
params.originHash = originHash; params.originHash = originHash;
params.listener = listener; params.listener = listener;
params.zipFilePath = new File(rootDir, originHash + "-" + hash + ".ppk.patch"); params.targetFile = new File(rootDir, originHash + "-" + hash + ".ppk.patch");
params.unzipDirectory = new File(rootDir, hash); params.unzipDirectory = new File(rootDir, hash);
params.originDirectory = new File(rootDir, originHash); params.originDirectory = new File(rootDir, originHash);
new DownloadTask(context).executeOnExecutor(this.executor, params); new DownloadTask(context).executeOnExecutor(this.executor, params);
@ -174,7 +185,7 @@ public class UpdateContext {
editor.remove("lastVersion"); editor.remove("lastVersion");
editor.apply(); editor.apply();
this.clearUp(); this.cleanUp();
} }
public void clearFirstTime() { public void clearFirstTime() {
@ -182,7 +193,7 @@ public class UpdateContext {
editor.putBoolean("firstTime", false); editor.putBoolean("firstTime", false);
editor.apply(); editor.apply();
this.clearUp(); this.cleanUp();
} }
public void clearRollbackMark() { public void clearRollbackMark() {
@ -190,7 +201,7 @@ public class UpdateContext {
editor.putBoolean("rolledBack", false); editor.putBoolean("rolledBack", false);
editor.apply(); editor.apply();
this.clearUp(); this.cleanUp();
} }
@ -256,21 +267,12 @@ public class UpdateContext {
return lastVersion; return lastVersion;
} }
private void clearUp() { private void cleanUp() {
DownloadTaskParams params = new DownloadTaskParams(); DownloadTaskParams params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_CLEARUP; params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
params.hash = sp.getString("currentVersion", null); params.hash = sp.getString("currentVersion", null);
params.originHash = sp.getString("lastVersion", null); params.originHash = sp.getString("lastVersion", null);
params.unzipDirectory = rootDir; params.unzipDirectory = rootDir;
params.listener = new DownloadFileListener() {
@Override
public void onDownloadCompleted() {
}
@Override
public void onDownloadFailed(Throwable error) {
}
};
new DownloadTask(context).executeOnExecutor(this.executor, params); new DownloadTask(context).executeOnExecutor(this.executor, params);
} }
} }

View File

@ -3,6 +3,8 @@ package cn.reactnative.modules.update;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log; import android.util.Log;
import com.facebook.react.ReactApplication; import com.facebook.react.ReactApplication;
@ -18,11 +20,14 @@ import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.io.File;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static android.support.v4.content.FileProvider.getUriForFile;
/** /**
* Created by tdzl2003 on 3/31/16. * Created by tdzl2003 on 3/31/16.
*/ */
@ -71,9 +76,9 @@ public class UpdateModule extends ReactContextBaseJavaModule{
public void downloadUpdate(ReadableMap options, final Promise promise){ public void downloadUpdate(ReadableMap options, final Promise promise){
String url = options.getString("updateUrl"); String url = options.getString("updateUrl");
String hash = options.getString("hash"); String hash = options.getString("hash");
updateContext.downloadFile(url, hash, new UpdateContext.DownloadFileListener() { updateContext.downloadFullUpdate(url, hash, new UpdateContext.DownloadFileListener() {
@Override @Override
public void onDownloadCompleted() { public void onDownloadCompleted(DownloadTaskParams params) {
promise.resolve(null); promise.resolve(null);
} }
@ -84,13 +89,58 @@ public class UpdateModule extends ReactContextBaseJavaModule{
}); });
} }
@ReactMethod
public void downloadAndInstallApk(ReadableMap options, final Promise promise){
String url = options.getString("url");
String hash = options.getString("hash");
String target = options.getString("target");
updateContext.downloadFile(url, hash, target, new UpdateContext.DownloadFileListener() {
@Override
public void onDownloadCompleted(DownloadTaskParams params) {
installApk(params.targetFile);
promise.resolve(null);
}
@Override
public void onDownloadFailed(Throwable error) {
promise.reject(error);
}
});
}
// install downloaded apk
@ReactMethod
public static void installApk(String url) {
File toInstall = new File(url);
installApk(toInstall);
}
public static void installApk(File toInstall) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = getUriForFile(mContext, mContext.getPackageName() + ".pushy.fileprovider", toInstall);
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(apkUri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} else {
Uri apkUri = Uri.fromFile(toInstall);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
}
@ReactMethod @ReactMethod
public void downloadPatchFromPackage(ReadableMap options, final Promise promise){ public void downloadPatchFromPackage(ReadableMap options, final Promise promise){
String url = options.getString("updateUrl"); String url = options.getString("updateUrl");
String hash = options.getString("hash"); String hash = options.getString("hash");
updateContext.downloadPatchFromApk(url, hash, new UpdateContext.DownloadFileListener() { updateContext.downloadPatchFromApk(url, hash, new UpdateContext.DownloadFileListener() {
@Override @Override
public void onDownloadCompleted() { public void onDownloadCompleted(DownloadTaskParams params) {
promise.resolve(null); promise.resolve(null);
} }
@ -108,7 +158,7 @@ public class UpdateModule extends ReactContextBaseJavaModule{
String originHash = options.getString("originHash"); String originHash = options.getString("originHash");
updateContext.downloadPatchFromPpk(url, hash, originHash, new UpdateContext.DownloadFileListener() { updateContext.downloadPatchFromPpk(url, hash, originHash, new UpdateContext.DownloadFileListener() {
@Override @Override
public void onDownloadCompleted() { public void onDownloadCompleted(DownloadTaskParams params) {
promise.resolve(null); promise.resolve(null);
} }

View File

@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="." path="."/>
</paths>

10
lib/index.d.ts vendored
View File

@ -43,6 +43,14 @@ export function switchVersionLater(hash: string): void;
export function markSuccess(): void; export function markSuccess(): void;
export async function downloadAndInstallApk({
url,
onDownloadProgress,
}: {
url: string;
onDownloadProgress?: (data: ProgressData) => void;
}): void;
/** /**
* @param {string} main - The main api endpoint * @param {string} main - The main api endpoint
* @param {string[]} [backups] - The back up endpoints. * @param {string[]} [backups] - The back up endpoints.
@ -55,7 +63,7 @@ export function setCustomEndpoints({
backupQueryUrl, backupQueryUrl,
}: { }: {
main: string; main: string;
backUps?: string[]; backups?: string[];
backupQueryUrl?: string; backupQueryUrl?: string;
}): void; }): void;

View File

@ -143,10 +143,11 @@ export async function downloadUpdate(options, eventListeners) {
if (!options.update) { if (!options.update) {
return; return;
} }
let progressHandler;
if (eventListeners) { if (eventListeners) {
if (eventListeners.onDownloadProgress) { if (eventListeners.onDownloadProgress) {
const downloadCallback = eventListeners.onDownloadProgress; const downloadCallback = eventListeners.onDownloadProgress;
eventEmitter.addListener('RCTPushyDownloadProgress', (progressData) => { progressHandler = eventEmitter.addListener('RCTPushyDownloadProgress', (progressData) => {
if (progressData.hash === options.hash) { if (progressData.hash === options.hash) {
downloadCallback(progressData); downloadCallback(progressData);
} }
@ -167,7 +168,7 @@ export async function downloadUpdate(options, eventListeners) {
hash: options.hash, hash: options.hash,
}); });
} }
eventEmitter.removeAllListeners('RCTPushyDownloadProgress'); progressHandler && progressHandler.remove();
return options.hash; return options.hash;
} }
@ -188,3 +189,22 @@ export function markSuccess() {
logger('markSuccess'); logger('markSuccess');
Pushy.markSuccess(); Pushy.markSuccess();
} }
export async function downloadAndInstallApk({ url, onDownloadProgress }) {
logger('downloadAndInstallApk');
let hash = Date.now().toString();
let progressHandler;
if (onDownloadProgress) {
progressHandler = eventEmitter.addListener('RCTPushyDownloadProgress', (progressData) => {
if (progressData.hash === hash) {
onDownloadProgress(progressData);
}
});
}
await Pushy.downloadAndInstallApk({
url,
target: 'update.apk',
hash,
});
progressHandler && progressHandler.remove();
}