WebSettings:對(duì)WebView進(jìn)行配置和管理
WebSettings webSettings = webView.getSettings();
//如果訪問的頁面中要與Javascript交互戳吝,則webview必須設(shè)置支持Javascript
webSettings.setJavaScriptEnabled(true);
//設(shè)置自適應(yīng)屏幕固蛾,兩者合用
//將圖片調(diào)整到適合webview的大小
webSettings.setUseWideViewPort(true);
// 縮放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
//支持內(nèi)容重新布局
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
//支持縮放,默認(rèn)為true哮伟。是下面那個(gè)的前提枉昏。
webSettings.setSupportZoom(true);
//設(shè)置內(nèi)置的縮放控件陈肛。若為false,則該WebView不可縮放
webSettings.setBuiltInZoomControls(true);
//隱藏原生的縮放控件
webSettings.setDisplayZoomControls(false);
//設(shè)置文本的縮放倍數(shù)兄裂,默認(rèn)為 100
webSettings.setTextZoom(2);
//提高渲染的優(yōu)先級(jí)
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
/設(shè)置 WebView 的字體句旱,默認(rèn)字體為 "sans-serif"
webSettings.setStandardFontFamily("");
//設(shè)置 WebView 字體的大小,默認(rèn)大小為 16
webSettings.setDefaultFontSize(20);
//設(shè)置 WebView 支持的最小字體大小晰奖,默認(rèn)為 8
webSettings.setMinimumFontSize(12);
// 5.1以上默認(rèn)禁止了https和http混用谈撒,以下方式是開啟
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
//設(shè)置可以訪問文件,當(dāng)需要選擇圖片時(shí),需要打開
webSettings.setAllowFileAccess(true);
//支持通過JS打開新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//支持自動(dòng)加載圖片
webSettings.setLoadsImagesAutomatically(true);
//設(shè)置編碼格式
webSettings.setDefaultTextEncodingName("utf-8");
//允許網(wǎng)頁執(zhí)行定位操作
webSettings.setGeolocationEnabled(true);
//設(shè)置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: 不使用網(wǎng)絡(luò)匾南,只讀取本地緩存數(shù)據(jù)
//LOAD_DEFAULT: (默認(rèn))根據(jù)cache-control決定是否從網(wǎng)絡(luò)上取數(shù)據(jù)啃匿。
//LOAD_NO_CACHE: 不使用緩存,只從網(wǎng)絡(luò)獲取數(shù)據(jù).
//LOAD_CACHE_ELSE_NETWORK,只要本地有溯乒,無論是否過期夹厌,或者no-cache,都使用緩存中的數(shù)據(jù)裆悄。
//優(yōu)先使用緩存:
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//不使用緩存:
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE)
WebSettings:WebView輸入框被遮擋處理
??當(dāng)我們?cè)赪ebView中使用輸入框往往會(huì)出現(xiàn)被遮擋情況
??首先矛纹,頁面是非全屏模式的情況下,給activity設(shè)置adjustPan會(huì)失效光稼。
??其次或南,頁面是全屏模式的情況,adjustPan跟adjustResize都會(huì)失效艾君。
——解釋一下采够,這里的全屏模式即是頁面是全屏的,包括Application或activity使用了Fullscreen主題冰垄、使用了『狀態(tài)色著色』吁恍、『沉浸式狀態(tài)欄』、『Immersive Mode』等等——總之播演,基本上只要是App自己接管了狀態(tài)欄的控制,就會(huì)產(chǎn)生這種問題伴奥。
??詳情可參考:AndroidBug5497Workaround
??這種坑我們命名為5467写烤,我們可以使用 AndroidBug5497Workaround 這個(gè)類直接操作,
看名字就知道拾徙,它是專門用來對(duì)付"5497"問題的洲炊,使用步驟也是超級(jí)簡(jiǎn)單:
??把AndroidBug5497Workaround類復(fù)制到項(xiàng)目中
??在需要填坑的activity的onCreate方法中添加一句AndroidBug5497Workaround.assistActivity(this)即可。
??提供一下這個(gè)類:
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界面上開發(fā)者所能控制的區(qū)域的根View。
如果Activity是全屏模式崖瞭,那么android.R.id.content就是占滿全部屏幕區(qū)域的狂巢。
如果Activity是普通的非全屏模式,那么android.R.id.content就是占滿除狀態(tài)欄之外的所有區(qū)域书聚。
其他情況唧领,如Activity是彈窗、或者7.0以后的分屏樣式等雌续,android.R.id.content也是彈窗的范圍或者分屏所在的半個(gè)屏幕——這些情況較少斩个,就暫且不考慮了。
??然后通過
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener({
possiblyResizeChildOfContent();
});
??對(duì)我們的視圖進(jìn)行監(jiān)聽驯杜,當(dāng)軟鍵盤彈出時(shí)會(huì)觸發(fā)我們這個(gè)事件受啥。
??然后獲取我們當(dāng)前界面可用的高度
private int computeUsableHeight() {
Rect rect = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(rect);
// rect.top其實(shí)是狀態(tài)欄的高度,如果是全屏主題,直接 return rect.bottom就可以了
return (rect.bottom - rect.top);
}
??View.getWindowVisibleDisplayFrame(Rect rect)滚局,這行代碼能夠獲取到的Rect——就是界面除去了標(biāo)題欄居暖、除去了被軟鍵盤擋住的部分,所剩下的矩形區(qū)域.
??所以核畴,最后一步膝但,就是把界面高度置為可用高度——大功告成。
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;
}
}
總結(jié)起來谤草,就是這樣:
普通Activity(不帶WebView)跟束,直接使用adjustpan或者adjustResize
如果帶WebView:
a) 如果非全屏模式,可以使用adjustResize
b) 如果是全屏模式丑孩,則使用AndroidBug5497Workaround進(jìn)行處理冀宴。
WebView添加Header
??在某些情況下我們可能需要通過給請(qǐng)求的url添加請(qǐng)求頭來產(chǎn)生會(huì)話,比如app跳轉(zhuǎn)到html需要html快速登錄温学,就可以將token添加在請(qǐng)求頭中略贮,下面有倆種方式可以:
??1:如果只需在初始加載的時(shí)候添加Header,那么比較簡(jiǎn)單仗岖,只需要這么寫即可:
Map<String, String> header = new HashMap<>();
header.put("token", "token");
webView.loadUrl(url, header);
??2: 那如果每個(gè)頁面都要做Header驗(yàn)證逃延,那么就要重寫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", "");//不區(qū)分大小寫的替換
}
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;
}
}
};
??攔截了請(qǐng)求并獲取到了全部請(qǐng)求頭轧拄,再通過builder添加我們需要的header即可,當(dāng)然WebSettings也提供了一種特殊的頭部揽祥,我們可以通過獲取WebView的WebSettings進(jìn)行設(shè)置,如下:
webSettings.setUserAgentString("user_Agent")
??需要和后臺(tái)進(jìn)行約定規(guī)則使用即可。
WebView實(shí)現(xiàn)上傳圖片
??這里我推薦大家直接重寫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;
}
/**
* 文件選擇點(diǎn)擊回調(diào)
* */
public interface FileCall {
//5.0以下點(diǎn)擊網(wǎng)頁中File Input標(biāo)簽時(shí)回調(diào)
void fileChoseLow(ValueCallback<Uri> uploadMsg);
//5.0和5.0以上點(diǎn)擊網(wǎng)頁中File Input標(biāo)簽時(shí)回調(diào)
void fileChoseHigh(ValueCallback<Uri[]> uploadMsg);
}
}
??需要重寫多個(gè)方法拄丰,有的同學(xué)可能注意到了,有了方法沒有被@Override修飾啊俐末,這是因?yàn)檫@幾個(gè)方法都是隱藏方法料按,我們直接寫就可以,最終都通過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;
//調(diào)用系統(tǒng)拍照時(shí),拍照后圖片的存放路徑
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調(diào)用
webSettings.setDomStorageEnabled(true);
//解決圖片不顯示
webSettings.setBlockNetworkImage(false);
//5.0開始WebView默認(rèn)不允許混合模式丽柿,https當(dāng)中不能加載http資源恢准,需要設(shè)置開啟
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);
}
/**
* 選擇圖片回調(diào)
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILECHOOSER_RESULTCODE) {
//5.0以下,選擇圖片后回調(diào)
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));
}
}
//沒有選擇也要調(diào)用一次onReceiveValue方法甫题,否則下次點(diǎn)擊上傳按鈕沒反應(yīng)
valueCallback.onReceiveValue(result);
valueCallback = null;
mCameraFilePath = null;
} else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
//5.0或5.0以以上馁筐,選擇圖片后回調(diào)
onActivityResultAboveL(requestCode, resultCode, data);
}
}
/**
* 5.0或5.0以以上,選擇圖片后回調(diào)
*/
@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以下坠非,調(diào)用選擇框敏沉,選擇拍照或者圖片
*
* @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);
}
/**
* 選擇拍照或者圖片時(shí)需要的最終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以上,調(diào)用選擇框,選擇拍照或者圖片
*/
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);
}
}
??可以看到我們的回調(diào)在這里實(shí)現(xiàn)盟迟,并且區(qū)分了不同版本的設(shè)備進(jìn)行選擇圖片秋泳,最終在onActivityResult回調(diào)中獲取到了我們選擇到的圖片,然后valueCallback或valueCallbacks將結(jié)果返還給WebView攒菠,從而完成上傳圖片這一功能迫皱。
這里我們還用到了一個(gè)工具類:
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有變化,需要其他配置下面一行代碼才會(huì)有作用
contentUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", apkFile);
} else {
contentUri = Uri.fromFile(apkFile);
}
return contentUri;
}
/**
* 通過Uri返回File文件
* 注意:通過相機(jī)的是類似
content://media/external/images/media/231321
* 通過相冊(cè)選擇的:file:///storage/sdcard0/DCIM/Camera/IMG_12312321.jpg
* 通過查詢獲取實(shí)際的地址
* @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;
}
}
??這是因?yàn)榉?wù)器對(duì)文件的后綴有判斷辖众,而我們獲取的uri可能是這樣的:
content://media/external/images/media/231321
截取最后面就沒有了圖片格式卓起,這是不行的。因此凹炸,使用此方法轉(zhuǎn)化一下戏阅。
??需要注意的是Mainfest不要忘記添加網(wǎng)絡(luò)和存儲(chǔ)權(quán)限:
<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+設(shè)備并且是https的話,建議大家在資源文件res下新建xml包啤它,下面再新建一個(gè)network_security_config.xml文件奕筐,內(nèi)容為:
<?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"
??即可大功告成.
??布局就不上傳了,只有一個(gè)WebView变骡,大家自行添加嘗試一下离赫。
WebView同步Cookie實(shí)現(xiàn)
??Cookie可以讓我們的在客戶端調(diào)用Web頁面的時(shí)候避免重復(fù)登錄,一次登錄成功后存儲(chǔ)服務(wù)端返回給我們的Cookie塌碌,然后下次進(jìn)來的時(shí)候笆怠,可以提前設(shè)置上,就可以避免重復(fù)登錄誊爹。
??這里需要說一下基礎(chǔ)概念,Cookie本身是放在WebView發(fā)起請(qǐng)求的header中的瓢捉,有的話就帶上频丘,沒的話就不發(fā)送,當(dāng)我們調(diào)用成功一次url過后泡态,Cookie的值會(huì)通過服務(wù)端的一個(gè)字段返回搂漠,名為:
Set-Cookie
??感興趣的同學(xué)可以重寫WebViewClient的shouldInterceptRequest方法。
??方法1 : 獲取和設(shè)置Cookie的方法
??使用網(wǎng)絡(luò)框架獲取服務(wù)端返回的內(nèi)容某弦,我們就可以看到這個(gè)字段桐汤,下面看一下我重寫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", "");//不區(qū)分大小寫的替換
}
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設(shè)置上,然后打斷點(diǎn)靶壮,看一下我訪問我們公司登錄url返回的內(nèi)容怔毛,如下圖:
??可以看到服務(wù)端返回的key為
Set-Cookie
??value為
zqmy.id=89c8dae4-da20-418f-bed5-44c98834b83c; Path=/; HttpOnly
??那我們只需要把這個(gè)值給存下來,然后下次請(qǐng)求的時(shí)候放到請(qǐng)求頭里面就可以了腾降,需要注意的是拣度,我們發(fā)給服務(wù)端時(shí)不是Set-Cookie,而是Cookie,這點(diǎn)需要注意抗果,比如我們可以直接這樣寫:
Request.Builder builder = new Request.Builder()
.url(url.trim())
.addHeader("Cookie","zqmy.id=6b8c537a-dcb1-49a6-99dd-9e0121ccd644; Path=/; HttpOnly");
??當(dāng)然了筋帖,是不是用Cookie作為key可以和后臺(tái)商議決定,反正都是請(qǐng)求頭冤馏。
??方法2 : 獲取和設(shè)置Cookie的方法
??但是這種設(shè)置Cookie方式比較麻煩日麸,我們可以也通過下面方法獲取我們目標(biāo)url返回的Set-Cookie,系統(tǒng)已經(jīng)給我們封裝了一個(gè)去設(shè)置Cookie和取Cookie的類逮光,CookieManager代箭,訪問過url后我們獲取Cookie可以通過:
CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie(url);
??所以一般Cookie獲取我們放在重寫WebViewClient類的onPageFinished方法中。那如何設(shè)置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設(shè)置上后還需要同步刷新WebView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
} else {
CookieSyncManager.createInstance(context);
CookieSyncManager.getInstance().sync();
}
}
??那么為了避免重復(fù)登錄梢卸,設(shè)置Cookie的代碼通常放在:
webView.loadUrl()
??之前調(diào)用即可,這里還要提一點(diǎn),比如你是這樣寫入的cookie:
String cookie = "key=value;time=content";
setCookie("host",cookie);
??那其實(shí)只把key=value寫入到了[host]這個(gè)域名下副女,為什么time=content沒有寫入呢蛤高,因?yàn)榉痔?hào)“;”是cookie默認(rèn)的分割符碑幅,cookie認(rèn)為出現(xiàn)“戴陡;”當(dāng)前的cookie的值就結(jié)束了。
那能不能不使用“沟涨;”去恤批;連接字符串呢?
最好不要這樣做裹赴,因?yàn)榇蠹叶颊J(rèn)為cookie的分隔符就是“喜庞;”,如果你換成了另外一種棋返,當(dāng)有的同事不知道的話延都,就出問題了。
正確的方式應(yīng)該是怎么樣的呢睛竣?
使用base64轉(zhuǎn)碼一下就可以了晰房,這樣做你還能把信息加密一次,當(dāng)然需要你跟h5的同學(xué)溝通一下射沟,他那邊拿到cookie的值需要base64一下殊者,這樣就完美了。
??方法一是為了便于大家理解Cookie的最終來源和設(shè)置验夯,開發(fā)中通常使用第二種方式猖吴,下面提供第二種方式的工具類:
/**
* 用于WebView的Cookie存取的工具
* 操作方式:
* 1:在webView.loadUrl()前調(diào)用CookieControl.getInstance().sync()
* 2:在onPageFinished里面調(diào)用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對(duì)應(yīng)的Cookie
*/
public void save(String url) {
if (isEmpty(url)) {
return;
}
CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie(url);
put(url, cookie);
}
/**
* 同步url對(duì)應(yīng)的Cookie
*/
public void sync(Context context, String url) {
String cookies = get(url);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
//刪除全部的Cookie,不建議這樣做挥转,因?yàn)闀?huì)導(dǎo)致其他全部的Cookie也失效
//cookieManager.removeAllCookie();
if (cookies == null) {
return;
}
cookieManager.setCookie(url, cookies);
//通過setCookie設(shè)置上后還需要同步刷新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輕量級(jí)緩存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;
}
/**
* 存儲(chǔ)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;
}
}
??使用起來也很簡(jiǎn)單距误,只需要:
1:在webView.loadUrl()前調(diào)用CookieControl.getInstance(content).sync()
2:在onPageFinished里面調(diào)用CookieControl.getInstance(context).save()
未完待續(xù)...