Kotlin安卓:实现okhttp3持续登录,同步到webview

经常用httpclient请求的的情况下,一般就是用jsoup解析,去爬数据,用okhttp3实现cookie的保存。

这里做的是用okhttp3实现登录请求,然后直接将登录后可访问的页面Cookie同步加载到webview里面去。

将账号与密码保存到shareP ····下次打开webview就可以直接访问已登录页面了。

下面开始具体实现步骤:语言:kotlin,库: okhttp3 组件: webview

先看看一个简单的页面:第一次打开弹出一个账户,填写我们需要的账号与密码

保存到:SharedPreferences。下次登录自动调用login方法,利用okhttp3去请求登录页面。

下面会具体讲。

null

看看代码部分:首先会检查是否添加过账号,添加了就调用登录方法,没有就弹出对话框(这个对话框不可以返回,触摸关闭哦!)

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
val pr = getSharedPreferences("login_flag", Context.MODE_PRIVATE)
login_flag = pr.getString("login_flag","null")
if(login_flag == "null"){//检查是否添加过账户
Toast.makeText(this@MainActivity,"未添加过账户",Toast.LENGTH_SHORT).show()
val dialog = AlertDialog.Builder(this@MainActivity)
val tem_layout = layoutInflater.inflate(R.layout.add,null)
dialog.setView(tem_layout)//这里用的是对话框的自定义view,导入一个写好的布局
.setCancelable(false)//设置不可返回,或者触碰空白取消
.setTitle("添加账户")//用kotlin对象表达实现匿名类,重写监听器。
.setPositiveButton("确认",object:DialogInterface.OnClickListener{
override fun onClick(dialog: DialogInterface?, which: Int) {
val login_new_name = tem_layout.findViewById<EditText>(R.id.add_name)//这里要获取布局内部控件
val login_new_pass = tem_layout.findViewById<EditText>(R.id.add_pass)
if(login_new_pass.text.toString()=="" && login_new_pass.text.toString()==""){
Toast.makeText(this@MainActivity,"不可以为空",Toast.LENGTH_SHORT).show()
}else{
val newPr = getSharedPreferences("login_info",Context.MODE_PRIVATE)
val pr_editor = newPr.edit()
pr_editor.putString("login_name",login_new_name.text.toString())
pr_editor.putString("login_pass",login_new_pass.text.toString())
pr_editor.apply()
val pr2 = getSharedPreferences("login_flag",Context.MODE_PRIVATE)
pr2.edit().putString("login_flag","true").apply()
Toast.makeText(this@MainActivity,"账号:${login_new_name.text.toString()},密码:${login_new_pass.text.toString()}",Toast.LENGTH_SHORT).show()
}

}
})
.setNegativeButton("取消",null)
.create()
.show()
}else{
val pr3 = getSharedPreferences("login_info",Context.MODE_PRIVATE)
val tem_name = pr3.getString("login_name","null")
val tem_pass = pr3.getString("login_pass","null")
Toast.makeText(this@MainActivity,"账号:${tem_name},密码:${tem_pass}",Toast.LENGTH_SHORT).show()
Toast.makeText(this@MainActivity,"正在登录",Toast.LENGTH_SHORT).show()
login.myLogin(tem_name,tem_pass,myhandle)//调用登录

}

null

下面看看登录类:

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
 class Login(){
var cookieStore = HashMap<String, List<Cookie>?>()
val logUrl = "https://www.login.com"//登录url
val myManage = "https://www.logined.com"//登录以后可以访问的URL
val client = OkHttpClient.Builder().cookieJar( object : CookieJar {
override fun saveFromResponse(httpUrl: HttpUrl, list: List<Cookie>)
{
//保存Cookie,主线程要用来同步cookie
cookieStore[httpUrl.host()] = list
}
override fun loadForRequest(httpUrl: HttpUrl): List<Cookie>
{
val cookies = cookieStore[httpUrl.host()]
return cookies ?: ArrayList()
}
}).build() //初始化请求


fun myLogin(user: String, pw: String, hand: Handler) {
val isFailLogin = Regex(".*bad.*")//判断是否成功
val isSuccLogin= Regex(".*ok.*")//判断是否成功
var res = ""
if (user.isNotEmpty() && pw.isNotEmpty()) {
//在主线程中开启一个网络线程

thread {
//发送参数
val myinfo = FormBody.Builder()
.add("admin_name", user)
.add("admin_pass", pw)
.build()
//构建请求
var request = Request.Builder().url(this.logUrl).post(myinfo).build()
var response = this.client.newCall(request).enqueue(object : Callback {//这里用的enqueue有个回调方法,重写可以实现检测请求失败的情况
override fun onResponse(call: Call, response: Response) {
var res = response.body()?.string()//这里string()一定不要使用第二次,比如,如果又用了一次打印xx.string(),就会崩溃!
Log.d("here",res)
//if(res!!.contains(isSuccLogin)){//自定判断登录,用regex检查或别的。。
var msg = Message()
msg.what = 1//判断登录,假设1登录ok
hand.sendMessage(msg)//发送消息到UI线程
}

override fun onFailure(call: Call, e: IOException) {
var msg: Message = Message()
msg.what = 4//请求失败
hand.sendMessage(msg)
}
})
}

//判断提取的信息是否完整
} else {
var msg: Message = Message()
msg.what = 4
hand.sendMessage(msg)
}}}

null

下面回到主线程:

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
主线程有三个东西:handle webview 还有一个用来同步cookie的方法 syncCookie()先看看handle
//这里用的对象表达式重写
var myhandle = object:Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
when(msg.what){
1 -> {//登录成功后
synCookie()//调用同步cookie
Toast.makeText(this@MainActivity,"登录ok",Toast.LENGTH_SHORT).show()
web.loadUrl(login.myManage)//去请求登录后需要的页面 }
2 -> {
}
}
Log.d("msg",msg.what.toString())
}}

看看webview的设置:

val web = findViewById<WebView>(R.id.web)
var web_set = web.settings
web_set.javaScriptEnabled = true//允许JS
web_set.javaScriptCanOpenWindowsAutomatically = true
//重写webClient
//return false 用来阻止超链接会跳到浏览器用的
web.webViewClient = object :WebViewClient(){
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
return false
}}
web.webChromeClient = WebChromeClient()

下面看看同步方法:AS会提示过时,不用理会


fun synCookie(){
CookieSyncManager.createInstance(this)
var cookM = CookieManager.getInstance()
cookM.setAcceptCookie(true)//允许Cookie
Log.d("cookie",login.cookieStore.toString())//这里的cookieStore是我们在登录类里定义的一个集合。存储了cookie
var sessionCookie: Cookie = login.cookieStore["www.xxx.com"]!![0]
//这是一个装有list的map集合。map键就是url的host部分只要www.xx.com部分,至于值就是0啦。
//打印看看里面都是啥,name是phpsessionid,value就是值,domain是域名。
Log.d("sess","name:${sessionCookie.name()};value:${sessionCookie.value()};domain:${sessionCookie.domain()}")
var cookies = sessionCookie.name() + "=" + sessionCookie.value() + ";domain=" + sessionCookie.domain()
cookM.setCookie("https://logined.com",cookies)//这个很关键,设置cookie,在指定的url上带上cookie,比如这里是需要登录才可以访问的url
CookieSyncManager.getInstance().sync()//同步一下
}

null

看一下:

成功的图:自动登录到logined页面了。

null

最后梳理一下:

分两个部分:请求类,主界面的handle

请求类定义了请求url,主界面的handle收到指定信息,实现是否调用同步方法进行webview的cookie同步(其实就是把cookie保留下来,再去调用webview时候,已经有了Cookie买就不用登录了。好了。完了,kotlin的资料太少,参考的都是java的,我不会Java。)