集成TBS 自带的webview使用起来很多网页不兼容,有很多坑,这里推荐使用TBS,也就是X5内核。
https://x5.tencent.com/tbs/sdk.html
接入文档:
https://x5.tencent.com/docs/access.html
SDK接入 jar包方式集成 您可将官网下载的jar包复制到您的App的libs目录,并且通过Add As Library的方式集成TBS SDK
Gradle方式集成 您可以在使用SDK的模块的dependencies中添加引用进行集成:
1 api 'com.tencent.tbs:tbssdk:44286'
kts
1 api("com.tencent.tbs:tbssdk:44286" )
使用到的网址 如果设备有防火墙 需要把下面的地址加入白名单,否则无法下载X5内核。
soft.imtt.qq.com
wss.tim.qq.com
log.tbs.qq.com
cfg.imtt.qq.com
tbs.imtt.qq.com
cdntips.net
tcdnos.com
混淆配置 为了保障X5功能的正常使用,您需要在您的proguard-rules.pro文件中添加如下配置:
1 2 3 4 5 6 7 8 9 10 -dontwarn dalvik.** -dontwarn com.tencent.smtt.** -keep class com.tencent.smtt.** { *; } -keep class com.tencent.tbs.** { *; }
添加权限和服务 在您的AndroidManifest.xml增加如下权限:
1 2 3 4 5 6 7 <uses-permission android:name ="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name ="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.READ_PHONE_STATE" /> <uses-permission android:name ="android.permission.GET_TASKS" />
在AndroidManifest.xml中增加内核首次加载时优化Service声明。
该Service仅在TBS内核首次Dex加载时触发并执行dex2oat任务,任务完成后自动结束。
1 2 3 4 <service android:name ="com.tencent.smtt.export.external.DexClassLoaderProviderService" android:label ="dexopt" android:process =":dexopt" />
安装TBS内核 官方下载 Application的onCreate中进行初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import android.content.Contextimport android.util.Logimport com.tencent.smtt.export.external.TbsCoreSettingsimport com.tencent.smtt.sdk.QbSdkimport com.tencent.smtt.sdk.TbsDownloaderimport com.tencent.smtt.sdk.TbsListenerobject TBSUtils { var isUseX5 = false fun initTBS (conntext: Context) { QbSdk.setDownloadWithoutWifi(true ) val map = HashMap<String, Any>() map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true QbSdk.initTbsSettings(map) QbSdk.setTbsListener( object : TbsListener { override fun onDownloadProgress (progress: Int) { Log.i("TBS_X5" , "onDownloadProgress -->下载X5内核进度:$progress" ) } override fun onDownloadFinish (progress: Int) { Log.i("TBS_X5" , "onDownloadFinish -->下载X5内核完成:$progress" ) } override fun onInstallFinish (progress: Int) { Log.i("TBS_X5" , "onInstallFinish -->安装X5内核完成" ) msgCallback?.let { it(true , "安装内核完成请重启应用" ) } GlobalScope.launch { delay(10000 ) withContext(Dispatchers.Main) { restartApp(activity) } } } } ) QbSdk.initX5Environment(conntext, object : QbSdk.PreInitCallback { override fun onCoreInitFinished () { Log.i("TBS_X5" , "内核初始化完成" ) } override fun onViewInitFinished (isX5: Boolean) { Log.i("TBS_X5" , "isX5: $isX5" ) isUseX5 = isX5 } }) } fun restartApp (activity: Activity) { val packageName: String = activity.getPackageName() val intent: Intent? = activity.getBaseContext().getPackageManager().getLaunchIntentForPackage(packageName) intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent?.let { activity.startActivity(intent) } activity.finish() } }
注意
不要在onViewInitFinished中自己调用下载,会和本身的下载冲突,导致下载失败。
1 2 3 4 5 6 7 override fun onViewInitFinished (isX5: Boolean ) { Log.i("TBS_X5" , "isX5: $isX5 " ) isUseX5 = isX5 if (!isX5) { TbsDownloader.startDownload(conntext); } }
使用本地文件 src/main下添加assets文件夹
文件夹中放入x5.tbs文件
Kotlin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 import android.content.Contextimport android.os.Environmentimport android.util.Logimport com.tencent.smtt.export.external .TbsCoreSettingsimport com.tencent.smtt.sdk.QbSdkimport com.tencent.smtt.sdk.TbsListenerimport kotlinx.coroutines.DelicateCoroutinesApiimport java.io.Fileimport java.io.FileOutputStreamobject TBSUtils { var isUseX5 = false var TAG = "TBS_X5" var msgCallback: ((show: Boolean , msg: String) -> Unit )? = null @OptIn(DelicateCoroutinesApi::class) fun initTBS (context: Context ) { val canLoadX5 = QbSdk.canLoadX5(context) Log.i(TAG, "canLoadX5:$canLoadX5 " ) if (!canLoadX5) { val appSavePath = getAppSavePath(context, "x5.tbs" ) Log.i(TAG, "initTBS: appSavePath:$appSavePath " ) copyFilesFassets(context, "x5.tbs" , appSavePath) QbSdk.reset(context); QbSdk.installLocalTbsCore(context, 46281 , appSavePath); } QbSdk.setDownloadWithoutWifi(true ) val map = HashMap<String, Any>() map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true QbSdk.initTbsSettings(map) QbSdk.setTbsListener( object : TbsListener { override fun onDownloadProgress (progress: Int ) { Log.i("TBS_X5" , "onDownloadProgress -->下载X5内核进度:$progress " ) msgCallback?.let { it(true , "下载内核进度:$progress %" ) } } override fun onDownloadFinish (progress: Int ) { Log.i("TBS_X5" , "onDownloadFinish -->下载X5内核完成:$progress " ) if (progress == 100 ) { msgCallback?.let { it(true , "下载内核成功" ) } } } override fun onInstallFinish (progress: Int ) { Log.i("TBS_X5" , "onInstallFinish -->安装X5内核完成" ) msgCallback?.let { it(true , "安装极速内核完成" ) } } } ) QbSdk.initX5Environment(context, object : QbSdk.PreInitCallback { override fun onCoreInitFinished () { Log.i("TBS_X5" , "内核初始化完成" ) } override fun onViewInitFinished (isX5: Boolean ) { Log.i("TBS_X5" , "isX5: $isX5 " ) isUseX5 = isX5 msgCallback?.let { it(false , "" ) } } }) } private fun copyFilesFassets (context: Context , oldPath: String , newPath: String ) { try { val fileNames = context.assets.list(oldPath) if (fileNames!!.isNotEmpty()) { val file = File(newPath) file.mkdirs() for (fileName in fileNames) { copyFilesFassets(context, "$oldPath /$fileName " , "$newPath /$fileName " ) } } else { val inputStream = context.assets.open (oldPath) val fos = FileOutputStream(File(newPath)) val buffer = ByteArray(1024 ) var byteCount: Int while (inputStream.read(buffer).also { byteCount = it } != -1 ) { fos.write(buffer, 0 , byteCount) } fos.flush() inputStream.close() fos.close() } } catch (e: Exception) { e.printStackTrace() } } private fun getFilePath (context: Context , dir: String ) : String { var directoryPath: String = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) { context.getExternalFilesDir(dir)!!.absolutePath } else { context.filesDir.toString() + File.separator + dir } val file = File(directoryPath) if (!file.exists()) { file.mkdirs() } return directoryPath } private fun getAppSavePath (context: Context , appname: String ) : String { val folderName = getFilePath(context, "app" ) return folderName + File.separator + appname } }
Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 import android.content.Context;import android.os.Environment;import android.util.Log;import com.tencent.smtt.export.external.TbsCoreSettings;import com.tencent.smtt.sdk.QbSdk;import com.tencent.smtt.sdk.TbsListener;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.util.HashMap;import java.util.Map;public class TBSUtils { public static boolean isUseX5 = false ; public static final String TAG = "TBS_X5" ; public interface MsgCallback { void onMessage (boolean show, String msg) ; } private static MsgCallback msgCallback; public static void setMsgCallback (MsgCallback callback) { msgCallback = callback; } public static void initTBS (Context context) { boolean canLoadX5 = QbSdk.canLoadX5(context); Log.i(TAG, "canLoadX5:" + canLoadX5); if (!canLoadX5) { String appSavePath = getAppSavePath(context, "x5.tbs" ); Log.i(TAG, "initTBS: appSavePath:" + appSavePath); copyFilesFassets(context, "x5.tbs" , appSavePath); QbSdk.reset(context); QbSdk.installLocalTbsCore(context, 46281 , appSavePath); } QbSdk.setDownloadWithoutWifi(true ); Map<String, Object> map = new HashMap <>(); map.put(TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER, true ); map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true ); map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true ); QbSdk.initTbsSettings(map); QbSdk.setTbsListener(new TbsListener () { @Override public void onDownloadProgress (int progress) { Log.i("TBS_X5" , "onDownloadProgress -->下载X5内核进度:" + progress); if (msgCallback != null ) { msgCallback.onMessage(true , "下载内核进度:" + progress + "%" ); } } @Override public void onDownloadFinish (int progress) { Log.i("TBS_X5" , "onDownloadFinish -->下载X5内核完成:" + progress); if (progress == 100 && msgCallback != null ) { msgCallback.onMessage(true , "下载内核成功" ); } } @Override public void onInstallFinish (int progress) { Log.i("TBS_X5" , "onInstallFinish -->安装X5内核完成" ); if (msgCallback != null ) { msgCallback.onMessage(true , "安装极速内核完成" ); } } }); QbSdk.initX5Environment(context, new QbSdk .PreInitCallback() { @Override public void onCoreInitFinished () { Log.i("TBS_X5" , "内核初始化完成" ); } @Override public void onViewInitFinished (boolean isX5) { Log.i("TBS_X5" , "isX5: " + isX5); isUseX5 = isX5; if (msgCallback != null ) { msgCallback.onMessage(false , "" ); } } }); } private static void copyFilesFassets (Context context, String oldPath, String newPath) { try { String[] fileNames = context.getAssets().list(oldPath); if (fileNames != null && fileNames.length > 0 ) { File dir = new File (newPath); if (!dir.exists()) { dir.mkdirs(); } for (String fileName : fileNames) { copyFilesFassets(context, oldPath + "/" + fileName, newPath + "/" + fileName); } } else { InputStream inputStream = context.getAssets().open(oldPath); File newFile = new File (newPath); File parent = newFile.getParentFile(); if (parent != null && !parent.exists()) { parent.mkdirs(); } FileOutputStream fos = new FileOutputStream (newFile); byte [] buffer = new byte [1024 ]; int byteCount; while ((byteCount = inputStream.read(buffer)) != -1 ) { fos.write(buffer, 0 , byteCount); } fos.flush(); fos.close(); inputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } private static String getFilePath (Context context, String dir) { String directoryPath; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { File externalDir = context.getExternalFilesDir(dir); if (externalDir != null ) { directoryPath = externalDir.getAbsolutePath(); } else { directoryPath = context.getFilesDir() + File.separator + dir; } } else { directoryPath = context.getFilesDir() + File.separator + dir; } File file = new File (directoryPath); if (!file.exists()) { file.mkdirs(); } return directoryPath; } private static String getAppSavePath (Context context, String appName) { String folderName = getFilePath(context, "app" ); return folderName + File.separator + appName; } }
其中复制的目标位置是
1 /sdcard/Android/data/com.xhkjedu.xh_control_browser/files/app/x5.tbs
使用自己的下载地址 默认使用官方下载,失败再使用自己的镜像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 import android.app.AlarmManagerimport android.app.PendingIntentimport android.content.Contextimport android.content.Intentimport android.os.Environmentimport android.util.Logimport com.billbook.lib.downloader.Downloadimport com.billbook.lib.downloader.Downloaderimport com.tencent.smtt.export.external .TbsCoreSettingsimport com.tencent.smtt.sdk.QbSdkimport com.tencent.smtt.sdk.TbsListenerimport kotlinx.coroutines.DelicateCoroutinesApiimport java.io.Fileobject TBSUtils { var isUseX5 = false var TAG = "TBS_X5" var tbsDownloadUrl = "https://xxx.oss-cn-huhehaote.aliyuncs.com/appmanager/tbscore/1/1/x5.tbs.apk" var msgCallback: ((show: Boolean , msg: String) -> Unit )? = null @OptIn(DelicateCoroutinesApi::class) fun initTBS (context: Context ) { QbSdk.setDownloadWithoutWifi(true ) QbSdk.setOnlyDownload(false ) val map = HashMap<String, Any>() map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true QbSdk.initTbsSettings(map) QbSdk.setTbsListener( object : TbsListener { override fun onDownloadProgress (progress: Int ) { Log.i("TBS_X5" , "onDownloadProgress -->下载X5内核进度:$progress " ) msgCallback?.let { it(true , "下载内核进度:$progress %" ) } } override fun onDownloadFinish (progress: Int ) { Log.i("TBS_X5" , "onDownloadFinish -->下载X5内核完成:$progress " ) if (progress == 100 ) { msgCallback?.let { it(true , "下载内核成功" ) } } } override fun onInstallFinish (progress: Int ) { Log.i("TBS_X5" , "onInstallFinish -->安装X5内核完成" ) msgCallback?.let { it(true , "安装极速内核完成" ) reStartApp(context) } } } ) QbSdk.initX5Environment(context, object : QbSdk.PreInitCallback { override fun onCoreInitFinished () { Log.i("TBS_X5" , "内核初始化完成" ) } override fun onViewInitFinished (isX5: Boolean ) { Log.i("TBS_X5" , "isX5: $isX5 " ) isUseX5 = isX5 msgCallback?.let { it(false , "" ) } if (!isX5){ downloadTbsCore(context) } } }) } private fun downloadTbsCore (context: Context ) { val appSavePath = getAppSavePath(context, "x5.tbs" ) val downloader = Downloader.Builder().build() val request = Download.Request.Builder() .url(tbsDownloadUrl) .into(appSavePath) .build() val call = downloader.newCall(request) call.enqueue(object : Download.Callback { override fun onStart (call: Download .Call ) { } override fun onSuccess (call: Download .Call , response: Download .Response ) { val canLoadX5 = QbSdk.canLoadX5(context) Log.i(TAG, "canLoadX5:$canLoadX5 " ) if (!canLoadX5) { QbSdk.reset(context); QbSdk.installLocalTbsCore(context, 46281 , appSavePath); } } override fun onFailure (call: Download .Call , response: Download .Response ) { } override fun onLoading (call: Download .Call , current: Long , total: Long ) { val progress = current * 100 / total Log.i(TAG, "镜像下载 -->下载X5内核完成:$progress " ) msgCallback?.let { it(true , "下载内核进度:${progress} %" ) } } }) } private fun getFilePath (context: Context , dir: String ) : String { val directoryPath: String = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) { context.getExternalFilesDir(dir)!!.absolutePath } else { context.filesDir.toString() + File.separator + dir } val file = File(directoryPath) if (!file.exists()) { file.mkdirs() } return directoryPath } private fun getAppSavePath (context: Context , appname: String ) : String { val folderName = getFilePath(context, "app" ) return folderName + File.separator + appname } fun reStartApp (context: Context ) { val intent: Intent? = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()) val pendingIntent = PendingIntent.getActivity(context, 0 , intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager? alarmManager!![AlarmManager.RTC, System.currentTimeMillis() + 1000 ] = pendingIntent System.exit(0 ) } }
TBS下载失败 TBS是分发的,也就是你调用下载,也不一定下载,这就导致内核安装变得让人难以捉摸。
这里建议下载成功的时候,不要重启找到下载的内核复制出来,千万不要重启,重启自动安装就会把下载的删除。
下载目录
假如我的APP的包名是com.xhkjedu.xh_control_browser
下载成功后会在下面的目录中产生一个x5.tbs文件
1 /data/data/com.xhkjedu.xh_control_browser/app_tbs_64/core_private
本地安装
1 2 3 4 5 6 7 8 9 val canLoadX5 = QbSdk.canLoadX5(context)Log.i(TAG, "canLoadX5:$canLoadX5 " ) if (!canLoadX5) { val appSavePath = getAppSavePath(context, "x5.tbs" ) Log.i(TAG, "initTBS: appSavePath:$appSavePath " ) copyFilesFassets(context, "x5.tbs" , appSavePath) QbSdk.reset(context); QbSdk.installLocalTbsCore(context, 46281 , appSavePath); }
引用的
1 api("com.tencent.tbs:tbssdk:44286" )
实际下载的是46281版本的x5.tbs
百度网盘下载x5.tbs
链接:https://pan.baidu.com/s/1Ly-ZQxv2nLtFG4txDic0Wg 提取码:psvm
重启APP 安装内核后重启才生效
1 2 3 4 5 6 7 fun reStartApp (context: Context ) { val intent: Intent? = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()) val pendingIntent = PendingIntent.getActivity(context, 0 , intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager? alarmManager!![AlarmManager.RTC, System.currentTimeMillis() + 1000 ] = pendingIntent System.exit(0 ) }
方式2
1 2 3 4 5 6 7 8 fun reStartApp2 () { try { Runtime.getRuntime().exec("am start -n com.xhkjedu.xh_control_browser/.MainActivity" ) Process.killProcess(Process.myPid()) } catch (e: IOException) { e.printStackTrace() } }
退出到主Launcher 我们应用的Activity都退出后,应用本身不一定退出,这是Android本身方便下次快速启动导致的。
但是TBS有时必须重启应用才能生效,但是直接退出应用,在管控平板上没有退出到上一个应用中,所以这里退出到主Launcher,再结束本应用。
1 2 3 4 5 6 7 8 9 override fun onBackPressed () { super .onBackPressed() if (!TBSUtils.isUseX5){ val intent = Intent(Intent.ACTION_MAIN) intent.addCategory(Intent.CATEGORY_HOME) startActivity(intent) System.exit(0 ) } }
测试是否生效 在没有自定义UA的情况下,使用您的app打开网页
1 http://soft.imtt.qq.com/browser/tes/feedback.html
显示000000表示加载的是系统内核,显示大于零的数字表示加载了x5内核(该数字是x5内核版本号)
替换 包名替换 将源码和XML里的系统包和类替换为SDK里的包和类,具体对应如下:
大部分是把android.webkit替换为com.tencent.smtt.sdk
具体表格
系统内核
SDK内核
android.webkit.ConsoleMessage
com.tencent.smtt.export.external.interfaces.ConsoleMessage
android.webkit.CacheManager
com.tencent.smtt.sdk.CacheManager(deprecated)
android.webkit.CookieManager
com.tencent.smtt.sdk.CookieManager
android.webkit.CookieSyncManager
com.tencent.smtt.sdk.CookieSyncManager
android.webkit.CustomViewCallback
com.tencent.smtt.export.external.interfaces.IX5WebChromeClient.CustomViewCallback
android.webkit.DownloadListener
com.tencent.smtt.sdk.DownloadListener
android.webkit.GeolocationPermissions
com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback
android.webkit.HttpAuthHandler
com.tencent.smtt.export.external.interfaces.HttpAuthHandler
android.webkit.JsPromptResult
com.tencent.smtt.export.external.interfaces.JsPromptResult
android.webkit.JsResult
com.tencent.smtt.export.external.interfaces.JsResult
android.webkit.SslErrorHandler
com.tencent.smtt.export.external.interfaces.SslErrorHandler
android.webkit.ValueCallback
com.tencent.smtt.sdk.ValueCallback
android.webkit.WebBackForwardList
com.tencent.smtt.sdk.WebBackForwardList
android.webkit.WebChromeClient
com.tencent.smtt.sdk.WebChromeClient
android.webkit.WebHistoryItem
com.tencent.smtt.sdk.WebHistoryItem
android.webkit.WebIconDatabase
com.tencent.smtt.sdk.WebIconDatabase
android.webkit.WebResourceResponse
com.tencent.smtt.export.external.interfaces.WebResourceResponse
android.webkit.WebSettings
com.tencent.smtt.sdk.WebSettings
android.webkit.WebSettings.LayoutAlgorithm
com.tencent.smtt.sdk.WebSettings.LayoutAlgorithm
android.webkit.WebStorage
com.tencent.smtt.sdk.WebStorage
android.webkit.WebView
com.tencent.smtt.sdk.WebView
android.webkit.WebViewClient
com.tencent.smtt.sdk.WebViewClient
XML替换 布局xml里的声明也需要替换,例如:
1 2 3 4 5 6 <com.tencent.smtt.sdk.WebView android:id ="@+id/mWebView" android:layout_width ="fill_parent" android:layout_height ="fill_parent" android:paddingLeft ="5dp" android:paddingRight ="5dp" />
调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @SuppressLint("SetJavaScriptEnabled" ) private fun loadUrl (url: String ) { val webSettings = mWebView.getSettings(); webSettings.setJavaScriptEnabled(true ); webSettings.setJavaScriptCanOpenWindowsAutomatically(true ); webSettings.setUseWideViewPort(true ); webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); webSettings.setDisplayZoomControls(false ); webSettings.setJavaScriptEnabled(true ); webSettings.setAllowFileAccess(true ); webSettings.setBuiltInZoomControls(true ); webSettings.setSupportZoom(true ); webSettings.setLoadWithOverviewMode(true ); webSettings.setMixedContentMode(WebSettings.LOAD_NORMAL); webSettings.setDomStorageEnabled(true ); webSettings.setGeolocationEnabled(true ); webSettings.setAppCacheMaxSize(Long .MAX_VALUE); webSettings.setPluginState(WebSettings.PluginState.ON_DEMAND); webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); try { mWebView.getSettingsExtension().setContentCacheEnable(true ); } catch (e: Exception) { } val jsstr = AndroidFileUtil.getFromAssets(this , "m_inject.js" ) mWebView.setWebChromeClient(object : WebChromeClient() { override fun onProgressChanged (p0: WebView ?, p1: Int ) { super .onProgressChanged(p0, p1) Log.e(TAG, "页面加载" + p1) mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> { override fun onReceiveValue (p0: String ?) { p0?.let { Log.e(TAG, it) } } }) } }); mWebView.setWebViewClient(object : WebViewClient() { override fun shouldOverrideUrlLoading ( view: WebView ?, request: WebResourceRequest ? ) : Boolean { view?.loadUrl(request?.url.toString()) return true } }); mWebView.loadUrl(url) } override fun onDestroy () { if (mWebView != null ) { mWebView.destroy() } super .onDestroy() } override fun onBackPressed () { if (mWebView.canGoBack()) { mWebView.goBack(); } else { super .onBackPressed() } }
JS注入 src/main下添加assets文件夹
添加m_inject.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var dom = document .getElementById ("menu-btn" );if (dom) { dom.style .display = "none" ; } var header = document .getElementsByClassName ("top-menu-basic" );if (header) { header[0 ].style .width = "100%" ; } var back = document .getElementsByClassName ("top3-back" );if (back) { var back_item = back[0 ]; back_item.onclick = function (event ) { history.back (-1 ); event.preventDefault (); }; }
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import android.content.Context;import java.io.BufferedReader;import java.io.InputStreamReader;public class AndroidFileUtil { public static String getFromRaw (Context mContext, int id) { try { InputStreamReader inputReader = new InputStreamReader (mContext.getResources().openRawResource(id)); BufferedReader bufReader = new BufferedReader (inputReader); String line = "" ; StringBuilder Result = new StringBuilder (); while ((line = bufReader.readLine()) != null ) Result.append(line); return Result.toString(); } catch (Exception e) { e.printStackTrace(); } return "" ; } public static String getFromAssets (Context mContext, String fileName) { try { InputStreamReader inputReader = new InputStreamReader (mContext.getResources().getAssets().open(fileName)); BufferedReader bufReader = new BufferedReader (inputReader); String line = "" ; StringBuilder Result = new StringBuilder (); while ((line = bufReader.readLine()) != null ) Result.append(line); return Result.toString(); } catch (Exception e) { e.printStackTrace(); } return "" ; } }
调用
1 2 3 4 5 6 val jsstr = AndroidFileUtil.getFromAssets(this , "m_inject.js" )mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> { override fun onReceiveValue (p0: String ?) { p0?.let { Log.e(TAG, it) } } })
我们可以在下面的回调中调用注入方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 mWebView.setWebChromeClient(object : WebChromeClient() { override fun onProgressChanged (p0: WebView ?, p1: Int ) { super .onProgressChanged(p0, p1) Log.e(TAG, "页面加载" + p1) if (p1 == 100 ){ } } }); mWebView.setWebViewClient(object : WebViewClient() { override fun onPageFinished (webView: WebView ?, s: String ?) { Log.e(TAG, "页面加载完成" ) super .onPageFinished(webView, s) } })
返回上一页 1 2 3 4 5 6 7 override fun onBackPressed () { if (mWebView.canGoBack()){ mWebView.goBack(); }else { super .onBackPressed() } }
申请权限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private val REQUEST_EXTERNAL_STORAGE = 1 private val PERMISSIONS_STORAGE = arrayOf( "android.permission.READ_EXTERNAL_STORAGE" , "android.permission.WRITE_EXTERNAL_STORAGE" , "android.permission.READ_PHONE_STATE" ) fun verifyStoragePermissions (activity: Activity ?) { try { val permission = ActivityCompat.checkSelfPermission( activity!!, "android.permission.WRITE_EXTERNAL_STORAGE" ) if (permission != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE ) } } catch (e: Exception) { e.printStackTrace() } }