项目场景:
在项目开发过程中,需要对图标进行网页跳转,尝试用WebView实现
第一个坑!
问题描述:
我首先上网查询WebView入门demo,类似于跳转百度等这样的小demo,然后写出一个activity用来绑定WebView布局
package com.sprocomm.tablelamp.ui.market;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.sprocomm.tablelamp.base.BaseActivity;
import com.sprocomm.tablelamp.databinding.ActivityStudyCloudBinding;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class StudyCloudActivity extends BaseActivity {
private ActivityStudyCloudBinding binding;
private final String originalUrl = "https://ykt.eduyun.cn/ykt/sjykt/index.html";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityStudyCloudBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.wvStudyCloud.loadUrl(originalUrl);
binding.wvStudyCloud.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/wv_study_cloud"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
如此一来,按照我们的预想,只要加载这个activity,就应该用webview显示咱们的目标url了,然鹅。。。 当我们通过其他地方的点击事件想要启动这个activity时,出现了这样的问题
解决方案:
通过面向百度、CSDN、简书等编程,我找到了这么一篇博客webViwe 报错 For security reasons, WebView is not allowed in privileged processes,根据文中描述,在Android8.0以后的版本中,如果你的app为系统app,则使用WebView会报错,这里我使用了博客中的方法二,在代码中加入了一个hookWebView() 方法
private static String TAG = "hookWebView";
public static void hookWebView(){
int sdkInt = Build.VERSION.SDK_INT;
try {
Class<?> factoryClass = Class.forName("android.webkit.WebViewFactory");
Field field = factoryClass.getDeclaredField("sProviderInstance");
field.setAccessible(true);
Object sProviderInstance = field.get(null);
if (sProviderInstance != null) {
Log.i(TAG,"sProviderInstance isn't null");
return;
}
Method getProviderClassMethod;
if (sdkInt > 22) {
getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
} else if (sdkInt == 22) {
getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
} else {
Log.i(TAG,"Don't need to Hook WebView");
return;
}
getProviderClassMethod.setAccessible(true);
Class<?> factoryProviderClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");
Constructor<?> delegateConstructor = delegateClass.getDeclaredConstructor();
delegateConstructor.setAccessible(true);
if(sdkInt < 26){
Constructor<?> providerConstructor = factoryProviderClass.getConstructor(delegateClass);
if (providerConstructor != null) {
providerConstructor.setAccessible(true);
sProviderInstance = providerConstructor.newInstance(delegateConstructor.newInstance());
}
} else {
@SuppressLint("SoonBlockedPrivateApi") Field chromiumMethodName = factoryClass.getDeclaredField("CHROMIUM_WEBVIEW_FACTORY_METHOD");
chromiumMethodName.setAccessible(true);
String chromiumMethodNameStr = (String)chromiumMethodName.get(null);
if (chromiumMethodNameStr == null) {
chromiumMethodNameStr = "create";
}
Method staticFactory = factoryProviderClass.getMethod(chromiumMethodNameStr, delegateClass);
if (staticFactory!=null){
sProviderInstance = staticFactory.invoke(null, delegateConstructor.newInstance());
}
}
if (sProviderInstance != null){
field.set("sProviderInstance", sProviderInstance);
Log.i(TAG,"Hook success!");
} else {
Log.i(TAG,"Hook failed!");
}
} catch (Throwable e) {
Log.w(TAG,e);
}
}
并在setContentView() 之前调用之
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hookWebView();
binding = ActivityStudyCloudBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.wvStudyCloud.loadUrl(originalUrl);
binding.wvStudyCloud.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
}
此时运行就能成功启动activity,并显示出网页
第二个坑!
问题描述:
上网搜WebView使用教程,都会介绍到WebView中的一些方法,例如:canGoBack() 、goBack() 。因为在实际使用中,用户会习惯使用返回键跳转网页,但是返回键会导致当前activity生命周期的结束,即finish() ,因此需要重写onBackPressed() 方法,即重写返回键的回调。而WebView提供的canGoBack() 、goBack() 方法就是用于判断当前网页是否可返回上一级网页,和执行返回的。因此重写onBackPressed() 方法可以如下:
@Override
public void onBackPressed() {
if (binding.wvStudyCloud.canGoBack()) {
binding.wvStudyCloud.goBack();
} else {
finish();
}
}
有上一级网页就返回网页,无上一级就finish() ,简单吧?要真有这么简单就好了。
上面的逻辑没有问题,可是实际运行时我发现,就算在网页主页按返回,也是不能实现finish() ,通过打 log 我发现,canGoBack() 的返回值不管我是否在网页的主页,一直都是true ,咋回事呢?
面向百度吧。
解决方案:
Android webView加载网页重定向导致canGoBack一直为true的解决方案 既然因为判断条件恒为true 导致页面无法finish掉,那么只要更改正确的判断条件就ok了,要判断当前网页是否是网页主页,可以根据url去判断,而WebView提供了获取当前url的方法getUrl() ,所以判断条件可以更换为
@Override
public void onBackPressed() {
Log.d("xumaoxin", "============================");
Log.d("xumaoxin", ""+ originalUrl);
Log.d("xumaoxin", ""+ binding.wvStudyCloud.getUrl());
if (binding.wvStudyCloud.canGoBack() && !(originalUrl.equals(binding.wvStudyCloud.getUrl()))) {
binding.wvStudyCloud.goBack();
} else {
finish();
}
}
originalUrl 是在创建类时定义的用于加载网页的url,如果当前url和originalUrl 不一致,则执行goBack() ;若一致,则证明当前已经在网页主页了,则执行finish() ;canGoBack() 的判断我没删,因为此处它不造成任何影响,就保留下来了。
这样,webView就可以正常工作了,本以为不难使用的一个WebView,弄了我三四个小时,属实难受,希望这个记录贴能帮助到看到的人,少走弯路,节约宝贵的时间。
最后修改好的完整代码如下(其中项目相关包名隐藏;BaseActivity是架构中所带,此处可以替换为AppCompatActivity;binding.wvStudyCloud 是ViewBinding的用法,不懂的可以用findViewById() ):
package ****;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import ****.BaseActivity;
import ****.ActivityStudyCloudBinding;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class StudyCloudActivity extends BaseActivity {
private ActivityStudyCloudBinding binding;
private final String originalUrl = "https://ykt.eduyun.cn/ykt/sjykt/index.html";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hookWebView();
binding = ActivityStudyCloudBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.wvStudyCloud.loadUrl(originalUrl);
binding.wvStudyCloud.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
}
@Override
public void onBackPressed() {
Log.d("xumaoxin", "============================");
Log.d("xumaoxin", ""+ originalUrl);
Log.d("xumaoxin", ""+ binding.wvStudyCloud.getUrl());
if (binding.wvStudyCloud.canGoBack() && !(originalUrl.equals(binding.wvStudyCloud.getUrl()))) {
binding.wvStudyCloud.goBack();
} else {
finish();
}
}
private static String TAG = "hookWebView";
public static void hookWebView(){
int sdkInt = Build.VERSION.SDK_INT;
try {
Class<?> factoryClass = Class.forName("android.webkit.WebViewFactory");
Field field = factoryClass.getDeclaredField("sProviderInstance");
field.setAccessible(true);
Object sProviderInstance = field.get(null);
if (sProviderInstance != null) {
Log.i(TAG,"sProviderInstance isn't null");
return;
}
Method getProviderClassMethod;
if (sdkInt > 22) {
getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
} else if (sdkInt == 22) {
getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
} else {
Log.i(TAG,"Don't need to Hook WebView");
return;
}
getProviderClassMethod.setAccessible(true);
Class<?> factoryProviderClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");
Constructor<?> delegateConstructor = delegateClass.getDeclaredConstructor();
delegateConstructor.setAccessible(true);
if(sdkInt < 26){
Constructor<?> providerConstructor = factoryProviderClass.getConstructor(delegateClass);
if (providerConstructor != null) {
providerConstructor.setAccessible(true);
sProviderInstance = providerConstructor.newInstance(delegateConstructor.newInstance());
}
} else {
@SuppressLint("SoonBlockedPrivateApi") Field chromiumMethodName = factoryClass.getDeclaredField("CHROMIUM_WEBVIEW_FACTORY_METHOD");
chromiumMethodName.setAccessible(true);
String chromiumMethodNameStr = (String)chromiumMethodName.get(null);
if (chromiumMethodNameStr == null) {
chromiumMethodNameStr = "create";
}
Method staticFactory = factoryProviderClass.getMethod(chromiumMethodNameStr, delegateClass);
if (staticFactory!=null){
sProviderInstance = staticFactory.invoke(null, delegateConstructor.newInstance());
}
}
if (sProviderInstance != null){
field.set("sProviderInstance", sProviderInstance);
Log.i(TAG,"Hook success!");
} else {
Log.i(TAG,"Hook failed!");
}
} catch (Throwable e) {
Log.w(TAG,e);
}
}
}
|