[英]Updating my app remotely without Google Play access
I am working on an app for my company's internal use which will collect performance stats from network and post them on our Grafana server.我正在开发一个供我公司内部使用的应用程序,它将从网络收集性能统计信息并将它们发布到我们的 Grafana 服务器上。 The app works fine with this context, but there is a problem: App will run on a phone at a datacenter and it will be very difficult to access it if we need to update the app for adding features.
该应用程序在此上下文中运行良好,但存在一个问题:应用程序将在数据中心的手机上运行,如果我们需要更新应用程序以添加功能,将很难访问它。 Also the phone will not have internet access.
此外,手机将无法访问互联网。 So I won't be able to update the app manually , or using Google Play.
所以我将无法手动更新应用程序,或使用 Google Play。 I thought of writing a function to check a static URL and when we put an updated apk there, it would download it and install.
我想写一个函数来检查静态 URL,当我们在那里放置更新的 apk 时,它会下载并安装。
I wrote this class (copying from another Stackoverflow question):我写了这个类(从另一个 Stackoverflow 问题复制):
class updateApp extends AsyncTask<String, Void, String> {
protected String doInBackground(String... sUrl) {
File file = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS),"updates");
if(!file.mkdir()){
}
File f = new File(file.getAbsolutePath(),"YourApp.apk");
Uri fUri = FileProvider.getUriForFile(MainActivity.this,"com.aktuna.vtv.monitor.fileprovider",f);
String path = fUri.getPath();
try {
URL url = new URL(sUrl[0]);
URLConnection connection = url.openConnection();
connection.connect();
InputStream input = new BufferedInputStream(url.openStream());
OutputStream output = new FileOutputStream(path);
byte data[] = new byte[1024];
int count;
while ((count = input.read(data)) != -1) {
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
} catch (Exception e) {
Log.e("YourApp", "Well that didn't work out so well...");
Log.e("YourApp", e.getMessage());
}
return path;
}
@Override
protected void onPostExecute(String path) {
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
File file = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS),"updates");
File f = new File(file.getAbsolutePath(),"YourApp.apk");
Uri fUri = FileProvider.getUriForFile(MainActivity.this,"com.aktuna.vtv.monitor.fileprovider",f);
i.setDataAndType(fUri, "application/vnd.android.package-archive" );
myCtx.startActivity(i);
}
}
It seems to download the file successfully.好像下载成功了。 And then it sends the file in the intent to the installer (I can see this because the packageinstaller selection prompt comes) But then it does not install the new apk.
然后它将意图中的文件发送到安装程序(我可以看到这一点,因为出现了 packageinstaller 选择提示)但是它不会安装新的 apk。
Since the previous Stackoverflow question is 7 years old, I thought that updating with no user interaction may be forbidden in new API levels.由于之前的 Stackoverflow 问题已有 7 年历史,因此我认为在新的 API 级别中可能会禁止在没有用户交互的情况下进行更新。
But I am not sure.但我不确定。 How can I troubleshoot this further ?
我怎样才能进一步解决这个问题?
Also, I am open to any suggestions to achieve this, maybe something making use of older API levels or anything that would solve the "updating with no internet access through an internal static URL" issue.此外,我乐于接受任何实现这一目标的建议,也许是利用旧 API 级别或任何可以解决“无法通过内部静态 URL 访问 Internet 进行更新”问题的建议。
Thanks.谢谢。
I followed recommendation from @keag and it worked.我遵循了@keag 的建议,并且奏效了。
1. With no "root" on the device, I made the app "device-owner" For this I added a device admin receiver class. 1. 在设备上没有“root”的情况下,我将应用程序设为“设备所有者”为此我添加了一个设备管理接收器类。 SampleAdminReceiver.class:
SampleAdminReceiver.class:
import android.app.admin.DeviceAdminReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class SampleAdminReceiver extends DeviceAdminReceiver {
void showToast(Context context, CharSequence msg) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onEnabled(Context context, Intent intent) {
showToast(context, "Device admin enabled");
}
@Override
public void onDisabled(Context context, Intent intent) {
showToast(context, "Device admin disabled");
}
}
added receiver to the manifest:将接收器添加到清单中:
<receiver
android:name=".SampleAdminReceiver"
android:description="@string/app_name"
android:label="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN" >
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/device_admin_receiver" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
Then using the adb interface I run the following dpm command:然后使用 adb 接口运行以下 dpm 命令:
$ dpm set-device-owner com.sample.app/.SampleAdminReceiver
Added following permission to manifest :添加了以下权限清单:
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
The with the following function I am able to install the apk from URL:具有以下功能,我可以从 URL 安装 apk:
public static boolean installPackageX(final Context context, final String url)
throws IOException {
//Use an async task to run the install package method
AsyncTask<Void,Void,Void> task = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
try {
PackageInstaller packageInstaller = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
packageInstaller = context.getPackageManager().getPackageInstaller();
}
PackageInstaller.SessionParams params = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
}
// set params
int sessionId = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
sessionId = packageInstaller.createSession(params);
}
PackageInstaller.Session session = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
session = packageInstaller.openSession(sessionId);
}
OutputStream out = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
out = session.openWrite("COSU", 0, -1);
}
//get the input stream from the url
HttpURLConnection apkConn = (HttpURLConnection) new URL(url).openConnection();
InputStream in = apkConn.getInputStream();
byte[] buffer = new byte[65536];
int c;
while ((c = in.read(buffer)) != -1) {
out.write(buffer, 0, c);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
session.fsync(out);
}
in.close();
out.close();
//you can replace this intent with whatever intent you want to be run when the applicaiton is finished installing
//I assume you have an activity called InstallComplete
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("info", "somedata"); // for extra data if needed..
Random generator = new Random();
PendingIntent i = PendingIntent.getActivity(context, generator.nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
session.commit(i.getIntentSender());
}
} catch (Exception ex){
ex.printStackTrace();
Log.e("AppStore","Error when installing application. Error is " + ex.getMessage());
}
return null;
}
};
task.execute(null,null);
return true;
}
After that, it is just a matter of automating the process.之后,这只是使过程自动化的问题。
Btw, following code in the app is useful for removing "device owner" property.顺便说一句,应用程序中的以下代码对于删除“设备所有者”属性很有用。
DevicePolicyManager dpm = (DevicePolicyManager) getApplicationContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.clearDeviceOwnerApp(getApplicationContext().getPackageName());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.