远程升级(静默升级)
作者:互联网
需求:公司有个广告投放的屏幕,需要在屏幕上进行广告播放,当app升级新东西的时候,对广告屏幕进行远程升级,这个是有root权限的,当时网上找了好多资料,然后结合自己整出来的这个,前面是工具类,最后面有使用方法和清单文件配置
public class AutoInstaller extends Handler {
private static final String TAG = AutoInstaller.class.getSimpleName();
private static final int REQUEST_CODE_PERMISSION_STORAGE = 100;
private static volatile AutoInstaller mAutoInstaller;
private Context mContext;
private String mTempPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Download";
public enum MODE {
ROOT_ONLY,
AUTO_ONLY,
BOTH
}
private MODE mMode = MODE.BOTH;
private AutoInstaller(Context context) {
mContext = context;
}
public static AutoInstaller getDefault(Context context) {
if (mAutoInstaller == null) {
synchronized (AutoInstaller.class) {
if (mAutoInstaller == null) {
mAutoInstaller = new AutoInstaller(context);
}
}
}
return mAutoInstaller;
}
public interface OnStateChangedListener {
void onStart();
void onComplete();
void onNeed2OpenService();
void needPermission();
}
private OnStateChangedListener mOnStateChangedListener;
public void setOnStateChangedListener(OnStateChangedListener onStateChangedListener) {
mOnStateChangedListener = onStateChangedListener;
}
public void execLinuxCommand(){
String cmd = "sleep 5; am start -n \"com.example.advertputproject/com.example.advertputproject.MainActivity\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER";
Runtime runtime = Runtime.getRuntime();
OutputStream outputStream=null;
try {
Process su = runtime.exec("su");
outputStream = su.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeBytes(cmd);
dataOutputStream.flush();
Log.d("凉城aa", "设备准备重启");
} catch (IOException pE) {
pE.printStackTrace();
Log.d("凉城aa", "failed"+pE.getMessage());
}finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException pE) {
pE.printStackTrace();
}
}
}
}
// public void autoRestart(Long delay){
// Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName());
// PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
// AlarmManager systemService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
// if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
// systemService.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+delay, pendingIntent);
// }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// systemService.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+delay, pendingIntent);
// }
// }
private boolean installUseRoot(String filePath) {
if (TextUtils.isEmpty(filePath))
throw new IllegalArgumentException("Please check apk file path!");
boolean result = false;
Process process = null;
OutputStream outputStream = null;
BufferedReader errorStream = null;
try {
process = Runtime.getRuntime().exec("su");
outputStream = process.getOutputStream();
String command = "pm install -r " + filePath + "\n"; // pm install -r 覆盖安装已存在Apk,并保持原有数据;
Log.d("凉城", command.toString());
outputStream.write(command.getBytes());
outputStream.flush();
outputStream.write("exit\n".getBytes());
outputStream.flush();
process.waitFor();
errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder msg = new StringBuilder();
String line;
while ((line = errorStream.readLine()) != null) {
msg.append(line);
}
Log.d(TAG, "install msg is " + msg);
if (!msg.toString().contains("Failure")) {
result = true;
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
result = false;
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
} catch (IOException e) {
outputStream = null;
errorStream = null;
process.destroy();
}
}
return result;
}
private void installUseAS(String filePath) {
// 存储空间
if (permissionDenied()) {
sendEmptyMessage(4);
return;
}
// 允许安装应用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean b = mContext.getPackageManager().canRequestPackageInstalls();
if (!b) {
sendEmptyMessage(4);
return;
}
}
File file = new File(filePath);
if (!file.exists()) {
Log.e(TAG, "apk file not exists, path: " + filePath);
return;
}
Uri uri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(mContext, "com.example.advertputproject.fileprovider", file);
mContext.grantUriPermission(mContext.getPackageName(), contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(uri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
mContext.startActivity(intent);
if (!isAccessibilitySettingsOn(mContext)) {
toAccessibilityService();
sendEmptyMessage(3);
}
}
private boolean permissionDenied() {
if (Build.VERSION.SDK_INT >= 23) {
String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
for (String str : permissions) {
if (mContext.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
return true;
}
}
}
return false;
}
private void toAccessibilityService() {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
mContext.startActivity(intent);
}
private boolean isAccessibilitySettingsOn(Context mContext) {
int accessibilityEnabled = 0;
final String service = mContext.getPackageName() + "/" + InstallAccessibilityService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(
mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED);
Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "Error finding setting, default accessibility to not found: "
+ e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------");
String settingValue = Settings.Secure.getString(
mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
Log.v(TAG, "-------------- > accessibilityService :: " + accessibilityService + " " + service);
if (accessibilityService.equalsIgnoreCase(service)) {
Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
return true;
}
}
}
} else {
Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
}
return false;
}
public void install(final String filePath) {
if (TextUtils.isEmpty(filePath) || !filePath.endsWith(".apk"))
throw new IllegalArgumentException("not a correct apk file path");
new Thread(new Runnable() {
@Override
public void run() {
sendEmptyMessage(1);
switch (mMode) {
case BOTH:
if (!Utils.checkRooted() || !installUseRoot(filePath)) {
installUseAS(filePath);
}
break;
case ROOT_ONLY:
installUseRoot(filePath);
break;
case AUTO_ONLY:
installUseAS(filePath);
}
sendEmptyMessage(0);
}
}).start();
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
if (mOnStateChangedListener != null)
mOnStateChangedListener.onComplete();
break;
case 1:
if (mOnStateChangedListener != null)
mOnStateChangedListener.onStart();
break;
case 3:
if (mOnStateChangedListener != null)
mOnStateChangedListener.onNeed2OpenService();
break;
case 4:
if (mOnStateChangedListener != null) {
mOnStateChangedListener.needPermission();
}
break;
}
}
public void install(File file) {
if (file == null)
throw new IllegalArgumentException("file is null");
install(file.getAbsolutePath());
}
public void installFromUrl(final String httpUrl) {
new Thread(new Runnable() {
@Override
public void run() {
sendEmptyMessage(1);
File file = downLoadFile(httpUrl);
// execLinuxCommand();
install(file);
}
}).start();
}
private File downLoadFile(String httpUrl) {
if (TextUtils.isEmpty(httpUrl)) throw new IllegalArgumentException();
String path = mContext.getFilesDir().getAbsolutePath();
File file = new File(path);
Log.d(TAG, "app路径:" + path);
if (!file.exists()) file.mkdirs();
file = new File(path + File.separator + "update.apk");
InputStream inputStream = null;
FileOutputStream outputStream = null;
HttpURLConnection connection = null;
try {
URL url = new URL(httpUrl);
connection = (HttpURLConnection) url.openConnection();
if (connection instanceof HttpsURLConnection) {
SSLContext sslContext = getSLLContext();
if (sslContext != null) {
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
}
}
connection.setConnectTimeout(60 * 1000);
connection.setReadTimeout(60 * 1000);
connection.connect();
inputStream = connection.getInputStream();
outputStream = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
if (outputStream != null)
outputStream.close();
if (connection != null)
connection.disconnect();
} catch (IOException e) {
inputStream = null;
outputStream = null;
}
}
return file;
}
private SSLContext getSLLContext() {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}}, new SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}
// public static class Builder {
//
// private MODE mode = MODE.BOTH;
// private Context context;
// private OnStateChangedListener onStateChangedListener;
// private String directory = Environment.getExternalStorageDirectory().getAbsolutePath();
//
// public Builder(Context c) {
// context = c;
// }
//
// public Builder setMode(MODE m) {
// mode = m;
// return this;
// }
//
// public Builder setOnStateChangedListener(OnStateChangedListener o) {
// onStateChangedListener = o;
// return this;
// }
//
// public Builder setCacheDirectory(String path) {
// directory = path;
// return this;
// }
//
// public AutoInstaller build() {
// AutoInstaller autoInstaller = new AutoInstaller(context);
// autoInstaller.mMode = mode;
// autoInstaller.mOnStateChangedListener = onStateChangedListener;
// autoInstaller.mTempPath = directory;
// return autoInstaller;
// }
//
// }
}
public class InstallAccessibilityService extends AccessibilityService {
private static final String TAG = InstallAccessibilityService.class.getSimpleName();
private Map<Integer, Boolean> handledMap = new HashMap<>();
private Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "onAccessibilityEvent: " + event.toString());
if (!String.valueOf(event.getPackageName()).contains("packageinstaller")) {
//不写完整包名,是因为某些手机(如小米)安装器包名是自定义的
return;
}
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo == null) {
Log.i(TAG, "eventNode: null, 重新获取eventNode...");
performGlobalAction(GLOBAL_ACTION_RECENTS); // 打开最近页面
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
performGlobalAction(GLOBAL_ACTION_BACK); // 返回安装页面
}
}, 320);
return;
}
int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED ||
eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
if (handledMap.get(event.getWindowId()) == null) {
boolean handled = iterateNodesAndHandle(nodeInfo);
if (handled) {
handledMap.put(event.getWindowId(), true);
}
}
}
}
private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo != null) {
int childCount = nodeInfo.getChildCount();
if ("android.widget.Button".equals(nodeInfo.getClassName())) {
String nodeContent = nodeInfo.getText().toString();
Log.d("TAG", "content is " + nodeContent);
if (!TextUtils.isEmpty(nodeContent)
&& ("安装".equals(nodeContent)
|| "install".equals(nodeContent.toLowerCase())
|| "done".equals(nodeContent.toLowerCase())
|| "完成".equals(nodeContent)
|| "确定".equals(nodeContent)
)) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return true;
}
} else if ("android.widget.ScrollView".equals(nodeInfo.getClassName())) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNodeInfo = nodeInfo.getChild(i);
if (iterateNodesAndHandle(childNodeInfo)) {
return true;
}
}
}
return false;
}
@Override
public void onInterrupt() {
}
}
public class Utils {
public static final String TAG = "Utils";
// 此方法工作有误
@Deprecated
public static boolean isRooted() {
Process process = null;
try {
process = Runtime.getRuntime().exec("su");
OutputStream outputStream = process.getOutputStream();
InputStream inputStream = process.getInputStream();
outputStream.write("id\n".getBytes());
outputStream.flush();
outputStream.write("exit\n".getBytes());
outputStream.flush();
process.waitFor();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
if (s.contains("uid=0")) return true;
} catch (IOException e) {
Log.e(TAG, "没有root权限");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (process != null)
process.destroy();
}
return false;
}
public static boolean checkRooted() {
boolean result = false;
try {
result = new File("/system/bin/su").exists() || new File("/system/xbin/su").exists();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
//应用
public class MainActivity extends AppCompatActivity {
private Banner banner;
public static final String APK_FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Download" + File.separator + "app-release.apk";
public static final String CACHE_FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Download";
public static final String APK_URL = "https://。。。。。/apk/app-release.apk"; //apk下载地址
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//沉浸式状态栏
ImmersionModeUtil.setStatusBar(this, false);
Log.d("凉城", "onCreate");
if (Build.VERSION.SDK_INT >= 23) {
int REQUEST_CODE_CONTACT = 101;
String[] permissions = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE};
//验证是否许可权限
for (String str : permissions) {
if (checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
//申请权限
requestPermissions(permissions, REQUEST_CODE_CONTACT);
initUpdate();
initView();
initData();
return;
} else {
//这里就是权限打开之后自己要操作的逻辑
initUpdate();
initView();
initData();
return;
}
}
}
}
private void initUpdate() {
String versionName = getAppVersionName(this);
Log.d("凉城versionName", versionName);
String url = "/data/user/0/com.example.advertputproject/files/update.apk"; ///下载后apk存在的位置,
//获取下载后apk的版本号
String apkVersionName = "";
PackageManager manager = getPackageManager();
PackageInfo info = manager.getPackageArchiveInfo(url, PackageManager.GET_ACTIVITIES);
if(info != null){
ApplicationInfo applicationInfo = info.applicationInfo;
applicationInfo.sourceDir = url;
applicationInfo.publicSourceDir = url;
String appName = manager.getApplicationLabel(applicationInfo).toString();// 得到应用名
String packageName = applicationInfo.packageName;// 得到包名
apkVersionName = info.versionName; // 得到版本信息
Log.d("凉城", "apkInfo----appName "+appName);
Log.d("凉城", "apkInfo----packageName "+packageName);
Log.d("凉城", "apkInfo----apkVersionName "+apkVersionName);
}
//如果下载的版本号和app的版本号一样就不升级
if(apkVersionName.equals(versionName)){
return;
}else {
AutoInstaller installer = AutoInstaller.getDefault(MainActivity.this);
installer.installFromUrl(APK_URL);
installer.setOnStateChangedListener(new AutoInstaller.OnStateChangedListener() {
@Override
public void onStart() {
}
@Override
public void onComplete() {
}
@Override
public void onNeed2OpenService() {
Toast.makeText(MainActivity.this, "请打开辅助功能服务", Toast.LENGTH_SHORT).show();
}
@Override
public void needPermission() {
Toast.makeText(MainActivity.this, "需要申请存储空间权限", Toast.LENGTH_SHORT).show();
}
});
}
}
private void initData() {
Retrofit build = new Retrofit.Builder()
.baseUrl(ApiService.baseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = build.create(ApiService.class);
Observable<BannerBean> imageUrl = apiService.getImageUrl();
imageUrl.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<BannerBean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(BannerBean pBannerBean) {
List<BannerBean.DataBean> data = pBannerBean.getData();
banner.setImages(data).setImageLoader(new ImageLoader() {
@Override
public void displayImage(Context context, Object path, ImageView imageView) {
BannerBean.DataBean bean = (BannerBean.DataBean) path;
Glide.with(MainActivity.this).load(bean.getImagePath()).into(imageView);
}
}).start();
}
@Override
public void one rror(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
private void initView() {
banner = (Banner) findViewById(R.id.banner);
}
升级完后发广播进行通知,重新启动app
public class AppInstallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
Toast.makeText(context, "安装成功", Toast.LENGTH_LONG).show();
AutoInstaller.getDefault(context).execLinuxCommand();
}
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
Toast.makeText(context, "卸载成功", Toast.LENGTH_LONG).show();
}
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
Toast.makeText(context, "替换成功", Toast.LENGTH_LONG).show();
AutoInstaller.getDefault(context).execLinuxCommand();//升级完后重新启动app
}
Log.d("凉城升级", "onReceive");
}
}
清单文件配置
<receiver
android:name=".AppInstallReceiver"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<service
android:name=".util.InstallAccessibilityService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
<!-- 文件访问 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.advertputproject.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
//file_paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="download"
path="Download" />
<external-path
name="ext_root"
path="/" />
<external-path
name="external_storage_root"
path="." />
<external-path
name="files_root"
path="Android/data/com.example.advertputproject/" />
</paths>
//accessibility_service_config
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description" />
标签:String,void,private,public,升级,静默,new,null,远程 来源: https://blog.csdn.net/qq_46237697/article/details/120265982