WebView详解
WebSettings:对WebView进行配置和管理
WebSettings webSettings = webView.getSettings();
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//设置自适应屏幕,两者合用
//将图片调整到适合webview的大小
webSettings.setUseWideViewPort(true);
// 缩放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
//支持内容重新布局
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
//支持缩放,默认为true。是下面那个的前提。
webSettings.setSupportZoom(true);
//设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setBuiltInZoomControls(true);
//隐藏原生的缩放控件
webSettings.setDisplayZoomControls(false);
//设置文本的缩放倍数,默认为 100
webSettings.setTextZoom(2);
//提高渲染的优先级
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
/设置 WebView 的字体,默认字体为 "sans-serif"
webSettings.setStandardFontFamily("");
//设置 WebView 字体的大小,默认大小为 16
webSettings.setDefaultFontSize(20);
//设置 WebView 支持的最小字体大小,默认为 8
webSettings.setMinimumFontSize(12);
// 5.1以上默认禁止了https和http混用,以下方式是开启
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
//设置可以访问文件,当需要选择图片时,需要打开
webSettings.setAllowFileAccess(true);
//支持通过JS打开新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//支持自动加载图片
webSettings.setLoadsImagesAutomatically(true);
//设置编码格式
webSettings.setDefaultTextEncodingName("utf-8");
//允许网页执行定位操作
webSettings.setGeolocationEnabled(true);
//设置User-Agent
webSettings.setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");
//不允许访问本地文件(不影响assets和resources资源的加载)
webSettings.setAllowFileAccess(false);
webSettings.setAllowFileAccessFromFileURLs(false);
webSettings.setAllowUniversalAccessFromFileURLs(false);
//缓存模式如下:
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
//优先使用缓存:
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//不使用缓存:
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE)
WebSettings:WebView输入框被遮挡处理
当我们在WebView中使用输入框往往会出现被遮挡情况
首先,页面是非全屏模式的情况下,给activity设置adjustPan会失效。
其次,页面是全屏模式的情况,adjustPan跟adjustResize都会失效。
——解释一下,这里的全屏模式即是页面是全屏的,包括Application或activity使用了Fullscreen主题、使用了『状态色着色』、『沉浸式状态栏』、『Immersive Mode』等等——总之,基本上只要是App自己接管了状态栏的控制,就会产生这种问题。
详情可参考:AndroidBug5497Workaround
这种坑我们命名为5467,我们可以使用 AndroidBug5497Workaround 这个类直接操作,
看名字就知道,它是专门用来对付"5497"问题的,使用步骤也是超级简单:
把AndroidBug5497Workaround类复制到项目中
在需要填坑的activity的onCreate方法中添加一句AndroidBug5497Workaround.assistActivity(this)即可。
提供一下这个类:
public class AndroidBug5497Workaround {
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity (Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);// 全屏模式下: return r.bottom
}
}
分析一下:
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
其中,第一行中的android.R.id.content所指的View,是Android所有Activity界面上开发者所能控制的区域的根View。
如果Activity是全屏模式,那么android.R.id.content就是占满全部屏幕区域的。
如果Activity是普通的非全屏模式,那么android.R.id.content就是占满除状态栏之外的所有区域。
其他情况,如Activity是弹窗、或者7.0以后的分屏样式等,android.R.id.content也是弹窗的范围或者分屏所在的半个屏幕——这些情况较少,就暂且不考虑了。
然后通过
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener({
possiblyResizeChildOfContent();
});
对我们的视图进行监听,当软键盘弹出时会触发我们这个事件。
然后获取我们当前界面可用的高度
private int computeUsableHeight() {
Rect rect = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(rect);
// rect.top其实是状态栏的高度,如果是全屏主题,直接 return rect.bottom就可以了
return (rect.bottom - rect.top);
}
View.getWindowVisibleDisplayFrame(Rect rect),这行代码能够获取到的Rect——就是界面除去了标题栏、除去了被软键盘挡住的部分,所剩下的矩形区域.
所以,最后一步,就是把界面高度置为可用高度——大功告成。
rivate void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
总结起来,就是这样:
普通Activity(不带WebView),直接使用adjustpan或者adjustResize
如果带WebView:
a) 如果非全屏模式,可以使用adjustResize
b) 如果是全屏模式,则使用AndroidBug5497Workaround进行处理。
WebView添加Header
在某些情况下我们可能需要通过给请求的url添加请求头来产生会话,比如app跳转到html需要html快速登录,就可以将token添加在请求头中,下面有俩种方式可以:
1:如果只需在初始加载的时候添加Header,那么比较简单,只需要这么写即可:
Map<String, String> header = new HashMap<>();
header.put("token", "token");
webView.loadUrl(url, header);
2: 那如果每个页面都要做Header验证,那么就要重写WebViewClient的shouldInterceptRequest方法了:
private WebViewClient mWebViewClient = new WebViewClient() {
//API until level 21
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
//API 21以下,此处不考虑兼容性
return super.shouldInterceptRequest(view, url);
}
//API 21+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
return getNewResponse(url, request.getRequestHeaders());
}
private WebResourceResponse getNewResponse(String url, Map<String, String> headers) {
try {
OkHttpClient httpClient = new OkHttpClient();
Request.Builder builder = new Request.Builder()
.url(url.trim())
.addHeader("token", "token");
Set<String> keySet = headers.keySet();
for (String key : keySet) {
builder.addHeader(key, headers.get(key));
}
Request request = builder.build();
final Response response = httpClient.newCall(request).execute();
String conentType = response.header("Content-Type", response.body().contentType().type());
String temp = conentType.toLowerCase();
if (temp.contains("charset=utf-8")) {
conentType = conentType.replaceAll("(?i)" + "charset=utf-8", "");//不区分大小写的替换
}
if (conentType.contains(";")) {
conentType = conentType.replaceAll(";", "");
conentType = conentType.trim();
}
return new WebResourceResponse(
conentType,
response.header("Content-Encoding", "utf-8"),
response.body().byteStream()
);
} catch (Exception e) {
return null;
}
}
};
拦截了请求并获取到了全部请求头,再通过builder添加我们需要的header即可,当然WebSettings也提供了一种特殊的头部,我们可以通过获取WebView的WebSettings进行设置,如下:
webSettings.setUserAgentString("user_Agent")
需要和后台进行约定规则使用即可。
WebView实现上传图片
这里我推荐大家直接重写WebChromeClient即可,代码如下:
public class MyChromeClient extends WebChromeClient {
private FileCall fileCall;
public MyChromeClient(FileCall fileCall) {
this.fileCall = fileCall;
}
/**
* Android 3.0+
*/
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
if (fileCall != null)
fileCall.fileChoseLow(uploadMsg);
}
/**
* Android < 3.0
*/
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
openFileChooser(uploadMsg, "");
}
/**
* Android > 4.1.1
* */
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
openFileChooser(uploadMsg, acceptType);
}
/**
* Android > 5.0
* */
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (fileCall != null)
fileCall.fileChoseHigh(filePathCallback);
return true;
}
/**
* 文件选择点击回调
* */
public interface FileCall {
//5.0以下点击网页中File Input标签时回调
void fileChoseLow(ValueCallback<Uri> uploadMsg);
//5.0和5.0以上点击网页中File Input标签时回调
void fileChoseHigh(ValueCallback<Uri[]> uploadMsg);
}
}
需要重写多个方法,有的同学可能注意到了,有了方法没有被@Override修饰啊,这是因为这几个方法都是隐藏方法,我们直接写就可以,最终都通过FileCall接口把操作传出去,再看下我们的主类。
public class MyViewActivity extends Activity implements MyChromeClient.FileCall {
private WebView webView;
private WebSettings webSettings;
private ValueCallback<Uri> valueCallback;
private ValueCallback<Uri[]> valueCallbacks;
public final static int FILECHOOSER_RESULTCODE = 1;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
//调用系统拍照时,拍照后图片的存放路径
private String mCameraFilePath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view);
initView();
initData();
}
private void initView() {
webView = findViewById(R.id.web_view);
}
private void initData() {
webSettings = webView.getSettings();
//选择图片等文件需要js的支持
webSettings.setJavaScriptEnabled(true);
//打开本地缓存提供JS调用
webSettings.setDomStorageEnabled(true);
//解决图片不显示
webSettings.setBlockNetworkImage(false);
//5.0开始WebView默认不允许混合模式,https当中不能加载http资源,需要设置开启
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
//避免打开外部浏览器
webView.setWebViewClient(new WebViewClient());
//拦截文件选择的操作
webView.setWebChromeClient(new MyChromeClient(this));
webView.loadUrl("填写需要上传文件的路径");
}
@Override
public void fileChoseLow(ValueCallback<Uri> uploadMsg) {
openFileChooserImpl(uploadMsg);
}
@Override
public void fileChoseHigh(ValueCallback<Uri[]> uploadMsg) {
openFileChooserImplForAndroid5(uploadMsg);
}
/**
* 选择图片回调
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILECHOOSER_RESULTCODE) {
//5.0以下,选择图片后回调
if (null == valueCallback) return;
Uri result = null;
if (resultCode == RESULT_OK) {
result = data == null ? null : data.getData();
if (result != null) {
//选择图库
result = FileSwitchUtil.getUri(this, new File(FileSwitchUtil.getRealFilePath(this, result)));
} else {
//拍照
result = FileSwitchUtil.getUri(this, new File(mCameraFilePath));
}
}
//没有选择也要调用一次onReceiveValue方法,否则下次点击上传按钮没反应
valueCallback.onReceiveValue(result);
valueCallback = null;
mCameraFilePath = null;
} else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
//5.0或5.0以以上,选择图片后回调
onActivityResultAboveL(requestCode, resultCode, data);
}
}
/**
* 5.0或5.0以以上,选择图片后回调
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != FILECHOOSER_RESULTCODE_FOR_ANDROID_5 || valueCallbacks == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null) results = new Uri[]{Uri.parse(dataString)};
}
}
valueCallbacks.onReceiveValue(results);
valueCallbacks = null;
}
/**
* 5.0以下,调用选择框,选择拍照或者图片
*
* @param uploadMsg
*/
private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
valueCallback = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
Intent chooser = createChooserIntent(createCameraIntent());
chooser.putExtra(Intent.EXTRA_INTENT, i);
startActivityForResult(chooser,
FILECHOOSER_RESULTCODE);
}
/**
* 选择拍照或者图片时需要的最终Intent
*/
private Intent createChooserIntent(Intent... intents) {
Intent chooser = new Intent(Intent.ACTION_CHOOSER);
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);
chooser.putExtra(Intent.EXTRA_TITLE, "选择图片");
return chooser;
}
/**
* 拍照需要的Intent
*/
@SuppressWarnings("static-access")
private Intent createCameraIntent() {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File externalDataDir = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
File cameraDataDir = new File(externalDataDir.getAbsolutePath()
+ File.separator + "telecom");
cameraDataDir.mkdirs();
mCameraFilePath = cameraDataDir.getAbsolutePath()
+ File.separator + System.currentTimeMillis() + ".jpg";
cameraIntent.putExtra(MediaStore.Images.Media.ORIENTATION, 0);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(new File(mCameraFilePath)));
return cameraIntent;
}
/**
* 5.0以上,调用选择框,选择拍照或者图片
*/
private void openFileChooserImplForAndroid5(ValueCallback<Uri[]> uploadMsg) {
valueCallbacks = uploadMsg;
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("image/*");
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "选择图片");
startActivityForResult(chooserIntent,
FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
}
}
可以看到我们的回调在这里实现,并且区分了不同版本的设备进行选择图片,最终在onActivityResult回调中获取到了我们选择到的图片,然后valueCallback或valueCallbacks将结果返还给WebView,从而完成上传图片这一功能。
这里我们还用到了一个工具类:
public class FileSwitchUtil {
public static Uri getUri(Context mContext, File apkFile) {
Uri contentUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//6.0以上uri有变化,需要其他配置下面一行代码才会有作用
contentUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", apkFile);
} else {
contentUri = Uri.fromFile(apkFile);
}
return contentUri;
}
/**
* 通过Uri返回File文件
* 注意:通过相机的是类似
content://media/external/images/media/231321
* 通过相册选择的:file:///storage/sdcard0/DCIM/Camera/IMG_12312321.jpg
* 通过查询获取实际的地址
* @param uri
* @return
*/
public static String getRealFilePath(final Context context, final Uri uri ) {
if ( null == uri ) return null;
final String scheme = uri.getScheme();
String data = null;
if ( scheme == null )
data = uri.getPath();
else if ( ContentResolver.SCHEME_FILE.equals( scheme ) ) {
data = uri.getPath();
} else if ( ContentResolver.SCHEME_CONTENT.equals( scheme ) ) {
Cursor cursor = context.getContentResolver().query( uri, new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null );
if ( null != cursor ) {
if ( cursor.moveToFirst() ) {
int index = cursor.getColumnIndex( MediaStore.Images.ImageColumns.DATA );
if ( index > -1 ) {
data = cursor.getString( index );
}
}
cursor.close();
}
}
return data;
}
}
这是因为服务器对文件的后缀有判断,而我们获取的uri可能是这样的:
content://media/external/images/media/231321
截取最后面就没有了图片格式,这是不行的。因此,使用此方法转化一下。
需要注意的是Mainfest不要忘记添加网络和存储权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
如果是9.0+设备并且是https的话,建议大家在资源文件res下新建xml包,下面再新建一个network_security_config.xml文件,内容为:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
最后在Manifest的Application里面配置:
android:networkSecurityConfig="@xml/network_security_config"
即可大功告成.
布局就不上传了,只有一个WebView,大家自行添加尝试一下。
WebView同步Cookie实现
Cookie可以让我们的在客户端调用Web页面的时候避免重复登录,一次登录成功后存储服务端返回给我们的Cookie,然后下次进来的时候,可以提前设置上,就可以避免重复登录。
这里需要说一下基础概念,Cookie本身是放在WebView发起请求的header中的,有的话就带上,没的话就不发送,当我们调用成功一次url过后,Cookie的值会通过服务端的一个字段返回,名为:
Set-Cookie
感兴趣的同学可以重写WebViewClient的shouldInterceptRequest方法。
方法1 : 获取和设置Cookie的方法
使用网络框架获取服务端返回的内容,我们就可以看到这个字段,下面看一下我重写WebViewClient的写法:
private WebResourceResponse getNewResponse(String url, Map<String, String> headers) {
try {
OkHttpClient httpClient = new OkHttpClient();
httpClient.cookieJar();
Request.Builder builder = new Request.Builder()
.url(url.trim());
Set<String> keySet = headers.keySet();
for (String key : keySet) {
builder.addHeader(key, headers.get(key));
}
Request request = builder.build();
final Response response = httpClient.newCall(request).execute();
String conentType = response.header("Content-Type", response.body().contentType().type());
String temp = conentType.toLowerCase();
if (temp.contains("charset=utf-8")) {
conentType = conentType.replaceAll("(?i)" + "charset=utf-8", "");//不区分大小写的替换
}
if (conentType.contains(";")) {
conentType = conentType.replaceAll(";", "");
conentType = conentType.trim();
}
return new WebResourceResponse(
conentType,
response.header("Content-Encoding", "utf-8"),
response.body().byteStream()
);
} catch (Exception e) {
return null;
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
CookieControl.getInstance().save(url);
}
};
通过
webView.setWebViewClient(mWebViewClient);
给我们的WebView设置上,然后打断点,看一下我访问我们公司登录url返回的内容,如下图:
1-1
可以看到服务端返回的key为
Set-Cookie
value为
zqmy.id=89c8dae4-da20-418f-bed5-44c98834b83c; Path=/; HttpOnly
那我们只需要把这个值给存下来,然后下次请求的时候放到请求头里面就可以了,需要注意的是,我们发给服务端时不是Set-Cookie,而是Cookie,这点需要注意,比如我们可以直接这样写:
Request.Builder builder = new Request.Builder()
.url(url.trim())
.addHeader("Cookie","zqmy.id=6b8c537a-dcb1-49a6-99dd-9e0121ccd644; Path=/; HttpOnly");
当然了,是不是用Cookie作为key可以和后台商议决定,反正都是请求头。
方法2 : 获取和设置Cookie的方法
但是这种设置Cookie方式比较麻烦,我们可以也通过下面方法获取我们目标url返回的Set-Cookie,系统已经给我们封装了一个去设置Cookie和取Cookie的类,CookieManager,访问过url后我们获取Cookie可以通过:
CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie(url);
所以一般Cookie获取我们放在重写WebViewClient类的onPageFinished方法中。那如何设置Cookie呢,通过以下代码:
public void sync(Context context, String url) {
String cookie = get(url);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
if (cookie == null || "".equal(cookie)) {
return;
}
cookieManager.setCookie(url, cookie);
//通过setCookie设置上后还需要同步刷新WebView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
} else {
CookieSyncManager.createInstance(context);
CookieSyncManager.getInstance().sync();
}
}
那么为了避免重复登录,设置Cookie的代码通常放在:
webView.loadUrl()
之前调用即可,这里还要提一点,比如你是这样写入的cookie:
String cookie = "key=value;time=content";
setCookie("host",cookie);
那其实只把key=value写入到了[host]这个域名下,为什么time=content没有写入呢,因为分号“;”是cookie默认的分割符,cookie认为出现“;”当前的cookie的值就结束了。
那能不能不使用“;”去;连接字符串呢?
最好不要这样做,因为大家都认为cookie的分隔符就是“;”,如果你换成了另外一种,当有的同事不知道的话,就出问题了。
正确的方式应该是怎么样的呢?
使用base64转码一下就可以了,这样做你还能把信息加密一次,当然需要你跟h5的同学沟通一下,他那边拿到cookie的值需要base64一下,这样就完美了。
方法一是为了便于大家理解Cookie的最终来源和设置,开发中通常使用第二种方式,下面提供第二种方式的工具类:
/**
* 用于WebView的Cookie存取的工具
* 操作方式:
* 1:在webView.loadUrl()前调用CookieControl.getInstance().sync()
* 2:在onPageFinished里面调用CookieControl.getInstance().save()即可
**/
public class CookieControl {
private CookieCache cookieCache;
private static volatile CookieControl cookieControl;
private static HashMap<String, String> cookies = new HashMap<>();
private CookieControl(Context context) {
cookieCache = CookieCache.getInstance(context);
}
public static CookieControl getInstance(Context context) {
if (cookieControl == null) {
synchronized (CookieControl.class) {
if (cookieControl == null) {
cookieControl = new CookieControl(context);
}
}
}
return cookieControl;
}
/**
* 保存url对应的Cookie
*/
public void save(String url) {
if (isEmpty(url)) {
return;
}
CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie(url);
put(url, cookie);
}
/**
* 同步url对应的Cookie
*/
public void sync(Context context, String url) {
String cookies = get(url);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
//删除全部的Cookie,不建议这样做,因为会导致其他全部的Cookie也失效
//cookieManager.removeAllCookie();
if (cookies == null) {
return;
}
cookieManager.setCookie(url, cookies);
//通过setCookie设置上后还需要同步刷新WebView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
} else {
CookieSyncManager.createInstance(context);
CookieSyncManager.getInstance().sync();
}
}
private void put(String url, String newCookie) {
if (newCookie == null) {
return;
}
cookies.put(url, newCookie);
cookieCache.put(url, newCookie);
}
private String get(String url) {
String cookie = cookies.get(url);
if (isEmpty(cookie)) {
cookie = cookieCache.get(url);
cookies.put(url, cookieCache.get(url));
}
return cookie == null ? "" : cookie;
}
private boolean isEmpty(String value) {
if (value == null || "".equals(value)) {
return true;
}
return false;
}
}
以及:
/**
* 使用SharedPreferences轻量级缓存Cookie即可
*/
public class CookieCache {
private static volatile CookieCache cookieCache;
private String cookieName = "CookieCache_Name";
private SharedPreferences cookieShared;
private SharedPreferences.Editor cookieEditor;
private CookieCache(Context context) {
cookieShared = context.getSharedPreferences(cookieName, Context.MODE_PRIVATE);
cookieEditor = cookieShared.edit();
}
public static CookieCache getInstance(Context context) {
if (cookieCache == null) {
synchronized (CookieCache.class) {
if (cookieCache == null) {
cookieCache = new CookieCache(context);
}
}
}
return cookieCache;
}
/**
* 存储Cookie
*/
public boolean put(String url, String cookie) {
if (isEmpty(url) || isEmpty(cookie)) {
return false;
}
cookieEditor.putString(url, cookie);
return cookieEditor.commit();
}
/**
* 获取Cookie
*/
public String get(String url) {
if (isEmpty(url)) {
return "";
}
return cookieShared.getString(url, "");
}
private boolean isEmpty(String value) {
if (value == null || "".equals(value)) {
return true;
}
return false;
}
}
使用起来也很简单,只需要:
1:在webView.loadUrl()前调用CookieControl.getInstance(content).sync()
2:在onPageFinished里面调用CookieControl.getInstance(context).save()
未完待续...