WebViewUpgrade.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. package com.norman.webviewup.lib;
  2. import android.content.Context;
  3. import android.content.SharedPreferences;
  4. import android.content.pm.ApplicationInfo;
  5. import android.content.pm.PackageInfo;
  6. import android.os.Build;
  7. import android.os.Handler;
  8. import android.os.HandlerThread;
  9. import android.os.IBinder;
  10. import android.os.IInterface;
  11. import android.os.Looper;
  12. import android.webkit.WebView;
  13. import com.norman.webviewup.lib.download.DownloadAction;
  14. import com.norman.webviewup.lib.download.DownloaderSink;
  15. import com.norman.webviewup.lib.hook.PackageManagerHook;
  16. import com.norman.webviewup.lib.hook.WebViewUpdateServiceHook;
  17. import com.norman.webviewup.lib.reflect.RuntimeAccess;
  18. import com.norman.webviewup.lib.service.interfaces.IServiceManager;
  19. import com.norman.webviewup.lib.service.interfaces.IWebViewFactory;
  20. import com.norman.webviewup.lib.service.interfaces.IWebViewUpdateService;
  21. import com.norman.webviewup.lib.util.ApkUtils;
  22. import java.io.File;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.Objects;
  26. import java.util.concurrent.atomic.AtomicBoolean;
  27. import java.util.concurrent.atomic.AtomicReference;
  28. public class WebViewUpgrade {
  29. private static final List<UpgradeCallback> UPGRADE_CALLBACK_LIST = new ArrayList<>();
  30. private static final String UPGRADE_DIRECTORY = "WebViewUpgrade";
  31. private static final int STATUS_UNINIT = 0;
  32. private static final int STATUS_RUNNING = 1;
  33. private static final int STATUS_FAIL = 2;
  34. private static final int STATUS_COMPLETE = 3;
  35. private static UpgradeOptions UPGRADE_OPTIONS;
  36. private static int UPGRADE_STATUS = STATUS_UNINIT;
  37. private static float UPGRADE_PROCESS;
  38. private static String SYSTEM_WEB_VIEW_PACKAGE_NAME;
  39. private static String SYSTEM_WEB_VIEW_PACKAGE_VERSION;
  40. private static Throwable UPGRADE_THROWABLE;
  41. private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
  42. public synchronized static void addUpgradeCallback(UpgradeCallback upgradeCallback) {
  43. if (upgradeCallback == null) return;
  44. if (UPGRADE_CALLBACK_LIST.contains(upgradeCallback)) return;
  45. UPGRADE_CALLBACK_LIST.add(upgradeCallback);
  46. }
  47. public synchronized static void removeUpgradeCallback(UpgradeCallback upgradeCallback) {
  48. if (upgradeCallback == null) return;
  49. if (!UPGRADE_CALLBACK_LIST.contains(upgradeCallback)) return;
  50. UPGRADE_CALLBACK_LIST.remove(upgradeCallback);
  51. }
  52. public synchronized static boolean isProcessing() {
  53. return UPGRADE_STATUS == STATUS_RUNNING;
  54. }
  55. public synchronized static boolean isCompleted() {
  56. return UPGRADE_STATUS == STATUS_COMPLETE;
  57. }
  58. public synchronized static boolean isFailed() {
  59. return UPGRADE_STATUS == STATUS_FAIL;
  60. }
  61. public synchronized static boolean isInited() {
  62. return UPGRADE_STATUS != STATUS_UNINIT;
  63. }
  64. public synchronized static Throwable getUpgradeError(){
  65. return UPGRADE_THROWABLE;
  66. }
  67. public synchronized static float getUpgradeProcess(){
  68. return UPGRADE_PROCESS;
  69. }
  70. public synchronized static void upgrade(UpgradeOptions options) {
  71. try {
  72. if (UPGRADE_STATUS == STATUS_RUNNING ||UPGRADE_STATUS == STATUS_COMPLETE ) {
  73. return;
  74. }
  75. UPGRADE_OPTIONS = options;
  76. UPGRADE_STATUS = STATUS_RUNNING;
  77. UPGRADE_THROWABLE = null;
  78. HandlerThread upgradeThread = new HandlerThread("WebViewUpgrade");
  79. upgradeThread.start();
  80. Handler upgradeHandler = new Handler(upgradeThread.getLooper());
  81. upgradeHandler.post(new UPGRADE_ACTION(upgradeHandler));
  82. } catch (Throwable throwable) {
  83. callErrorCallback(throwable);
  84. }
  85. }
  86. static class UPGRADE_ACTION implements Runnable {
  87. private final Handler handler;
  88. private Context context;
  89. private String apkUrl;
  90. private String packageName;
  91. private String versionName;
  92. private String apkPath;
  93. private String soLibDir;
  94. private String soLibInstallCompleteKey;
  95. private SharedPreferences sharedPreferences;
  96. public UPGRADE_ACTION(Handler handler) {
  97. this.handler = handler;
  98. }
  99. @Override
  100. public void run() {
  101. try {
  102. UpgradeOptions options = UPGRADE_OPTIONS;
  103. DownloaderSink downloaderSink = options.downloaderSink;
  104. context = options.context;
  105. apkUrl = options.url;
  106. packageName = options.packageName;
  107. versionName = options.versionName;
  108. apkPath = new File(context.getFilesDir(),
  109. UPGRADE_DIRECTORY
  110. + "/" + packageName
  111. + "/" + versionName
  112. + "/base.apk").getAbsolutePath();
  113. soLibDir = new File(context.getFilesDir(),
  114. UPGRADE_DIRECTORY
  115. + "/" + packageName
  116. + "/" + versionName
  117. + "/libs").getAbsolutePath();
  118. sharedPreferences = context
  119. .getSharedPreferences(
  120. UPGRADE_DIRECTORY,
  121. Context.MODE_PRIVATE);
  122. soLibInstallCompleteKey = packageName + ":" + versionName;
  123. File soDir = new File(soLibDir);
  124. if (!soDir.exists()) {
  125. soDir.mkdirs();
  126. }
  127. boolean installComplete = sharedPreferences
  128. .getBoolean(soLibInstallCompleteKey, false);
  129. if (installComplete) {
  130. upgradeWebView();
  131. } else {
  132. DownloadAction downloadAction = downloaderSink.createDownload(apkUrl, apkPath);
  133. if (downloadAction.isCompleted()) {
  134. extractNativeLibrary();
  135. upgradeWebView();
  136. } else {
  137. downloadAction.addCallback(new DownloadAction.Callback() {
  138. @Override
  139. public void onComplete(String path) {
  140. handler.post(() -> {
  141. try {
  142. extractNativeLibrary();
  143. upgradeWebView();
  144. }catch (Throwable throwable){
  145. callErrorCallback(throwable);
  146. handler.getLooper().quit();
  147. }
  148. });
  149. }
  150. @Override
  151. public void onFail(Throwable throwable) {
  152. callErrorCallback(throwable);
  153. handler.getLooper().quit();
  154. }
  155. @Override
  156. public void onProcess(float percent) {
  157. callProcessCallback(percent * 0.90f);
  158. }
  159. });
  160. }
  161. downloadAction.start();
  162. }
  163. } catch (Throwable throwable) {
  164. callErrorCallback(throwable);
  165. handler.getLooper().quit();
  166. }
  167. }
  168. private void extractNativeLibrary() {
  169. callProcessCallback(0.92f);
  170. ApkUtils.extractNativeLibrary(apkPath, soLibDir);
  171. callProcessCallback(0.94f);
  172. sharedPreferences
  173. .edit()
  174. .putBoolean(soLibInstallCompleteKey, true)
  175. .commit();
  176. }
  177. private void upgradeWebView() {
  178. callProcessCallback(0.95f);
  179. replaceWebViewProvider(context,
  180. packageName,
  181. versionName,
  182. apkPath,
  183. soLibDir);
  184. handler.getLooper().quit();
  185. }
  186. }
  187. private static void replaceWebViewProvider(Context context,
  188. String packageName,
  189. String versionName,
  190. String apkPath,
  191. String soLibDir) {
  192. PackageManagerHook managerHook = null;
  193. WebViewUpdateServiceHook updateServiceHook = null;
  194. try {
  195. callProcessCallback(0.96f);
  196. PackageInfo packageInfo = context.getPackageManager()
  197. .getPackageArchiveInfo(apkPath, 0);
  198. if (packageInfo == null) {
  199. throw new NullPointerException("path: " + apkPath + " is not apk");
  200. }
  201. if (!Objects.equals(packageInfo.packageName, packageName)) {
  202. throw new IllegalArgumentException("packageName:"
  203. + packageInfo.packageName
  204. + " in the options is different from packageName:"
  205. + packageName + " in the apk");
  206. }
  207. if (!Objects.equals(packageInfo.versionName, versionName)) {
  208. throw new IllegalArgumentException("versionName:"
  209. + packageInfo.versionName
  210. + " in the options is different from versionName:"
  211. + versionName + " in the apk");
  212. }
  213. int sdkVersion = Build.VERSION.SDK_INT;
  214. ApplicationInfo applicationInfo = packageInfo.applicationInfo;
  215. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  216. if (sdkVersion < applicationInfo.minSdkVersion) {
  217. throw new RuntimeException("The current system version " + sdkVersion + " is smaller than the minimum version " + applicationInfo.minSdkVersion + "required by the apk " + apkPath);
  218. }
  219. }
  220. checkWebView();
  221. managerHook = new PackageManagerHook(context, packageName, apkPath, soLibDir);
  222. updateServiceHook = new WebViewUpdateServiceHook(context, packageName);
  223. managerHook.hook();
  224. callProcessCallback(0.97f);
  225. updateServiceHook.hook();
  226. callProcessCallback(0.98f);
  227. Object lock = new Object();
  228. AtomicBoolean loadOver = new AtomicBoolean();
  229. AtomicReference<Throwable> throwableReference = new AtomicReference<>(null);
  230. MAIN_HANDLER.post(() -> {
  231. try {
  232. loadSystemWebViewPackage();
  233. checkWebView();
  234. new WebView(context);
  235. } catch (Throwable throwable) {
  236. throwableReference.set(throwable);
  237. } finally {
  238. synchronized (lock) {
  239. loadOver.set(true);
  240. lock.notifyAll();
  241. }
  242. }
  243. });
  244. synchronized (lock) {
  245. long startTime = System.currentTimeMillis();
  246. while (!loadOver.get()) {
  247. try {
  248. lock.wait(100);
  249. } catch (InterruptedException ignore) {
  250. }
  251. if ((System.currentTimeMillis() - startTime) > 5000) {
  252. throwableReference.set(new RuntimeException("webView load timeOut"));
  253. break;
  254. }
  255. }
  256. }
  257. Throwable throwable = throwableReference.get();
  258. if (throwable != null) {
  259. throw new RuntimeException(throwable);
  260. }
  261. callProcessCallback(1.0f);
  262. callCompleteCallback();
  263. } finally {
  264. if (managerHook != null) {
  265. managerHook.restore();
  266. }
  267. if (updateServiceHook != null) {
  268. updateServiceHook.restore();
  269. }
  270. }
  271. }
  272. private static void checkWebView() {
  273. IWebViewFactory webViewFactory = RuntimeAccess.staticAccess(IWebViewFactory.class);
  274. Object providerInstance = webViewFactory.getProviderInstance();
  275. if (providerInstance != null) {
  276. throw new IllegalStateException("WebViewProvider has been created, and the upgrade function can only be used before the webview is instantiated");
  277. }
  278. }
  279. private static void callErrorCallback(Throwable throwable) {
  280. synchronized (WebViewUpgrade.class) {
  281. UPGRADE_STATUS = STATUS_FAIL;
  282. UPGRADE_THROWABLE = throwable;
  283. }
  284. runInMainThread(() -> {
  285. synchronized (WebViewUpgrade.class) {
  286. for (UpgradeCallback upgradeCallback : UPGRADE_CALLBACK_LIST) {
  287. upgradeCallback.onUpgradeError(throwable);
  288. }
  289. }
  290. });
  291. }
  292. private static void callProcessCallback(float percent) {
  293. synchronized (WebViewUpgrade.class){
  294. UPGRADE_PROCESS = percent;
  295. }
  296. runInMainThread(() -> {
  297. synchronized (WebViewUpgrade.class) {
  298. for (UpgradeCallback upgradeCallback : UPGRADE_CALLBACK_LIST) {
  299. upgradeCallback.onUpgradeProcess(percent);
  300. }
  301. }
  302. });
  303. }
  304. private static void callCompleteCallback() {
  305. synchronized (WebViewUpgrade.class) {
  306. UPGRADE_STATUS = STATUS_COMPLETE;
  307. }
  308. runInMainThread(() -> {
  309. synchronized (WebViewUpgrade.class) {
  310. for (UpgradeCallback upgradeCallback : UPGRADE_CALLBACK_LIST) {
  311. upgradeCallback.onUpgradeComplete();
  312. }
  313. }
  314. });
  315. }
  316. private static void runInMainThread(Runnable runnable) {
  317. if (Looper.myLooper() == Looper.getMainLooper()) {
  318. runnable.run();
  319. } else {
  320. MAIN_HANDLER.post(runnable);
  321. }
  322. }
  323. public synchronized static String getSystemWebViewPackageName() {
  324. if (SYSTEM_WEB_VIEW_PACKAGE_NAME != null) {
  325. return SYSTEM_WEB_VIEW_PACKAGE_NAME;
  326. }
  327. loadSystemWebViewPackage();
  328. return SYSTEM_WEB_VIEW_PACKAGE_NAME;
  329. }
  330. public synchronized static String getSystemWebViewPackageVersion() {
  331. if (SYSTEM_WEB_VIEW_PACKAGE_VERSION != null) {
  332. return SYSTEM_WEB_VIEW_PACKAGE_VERSION;
  333. }
  334. loadSystemWebViewPackage();
  335. return SYSTEM_WEB_VIEW_PACKAGE_VERSION;
  336. }
  337. public synchronized static String getUpgradeWebViewPackageName() {
  338. return UPGRADE_OPTIONS != null ? UPGRADE_OPTIONS.packageName : null;
  339. }
  340. public synchronized static String getUpgradeWebViewVersion() {
  341. return UPGRADE_OPTIONS != null ? UPGRADE_OPTIONS.versionName : null;
  342. }
  343. private static void loadSystemWebViewPackage() {
  344. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  345. try {
  346. PackageInfo packageInfo = WebView.getCurrentWebViewPackage();
  347. SYSTEM_WEB_VIEW_PACKAGE_NAME = packageInfo.packageName;
  348. SYSTEM_WEB_VIEW_PACKAGE_VERSION = packageInfo.versionName;
  349. } catch (Throwable ignore) {
  350. }
  351. }
  352. if (SYSTEM_WEB_VIEW_PACKAGE_NAME == null) {
  353. try {
  354. IServiceManager serviceManager = RuntimeAccess.staticAccess(IServiceManager.class);
  355. IBinder binder = serviceManager.getService(IWebViewUpdateService.SERVICE);
  356. IWebViewUpdateService service = RuntimeAccess.staticAccess(IWebViewUpdateService.class);
  357. IInterface iInterface = service.asInterface(binder);
  358. service = RuntimeAccess.objectAccess(IWebViewUpdateService.class, iInterface);
  359. PackageInfo packageInfo = service.getCurrentWebViewPackage();
  360. SYSTEM_WEB_VIEW_PACKAGE_NAME = packageInfo.packageName;
  361. SYSTEM_WEB_VIEW_PACKAGE_VERSION = packageInfo.versionName;
  362. } catch (Throwable ignore) {
  363. }
  364. }
  365. }
  366. }