0%

封装独立进程的WebView


对于WebView的重度使用,如游戏音视频比较耗内存的需求,把WebView放到一个新的进程可以申请到更大的内存,修改其Activity的进程,通过AIDL与主线程通信,与主进程隔离,避免WebView的不稳定性导致所在进程异常影响主进程的正常运行造成不必要的crash,在此记录一下封装过程,欢迎一起学习和讨论~


封装WebView

自定义WebView

初始化WebView默认设置,设置javascript调用接口,回调js的dispatchEvent方法做事件分发,web进程初始化主进程调用接口,加载url时重置touch状态,监听用户的触摸操作,通过是否为用户点击(touch状态)判断当前跳转是否为重定向

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
open class BaseWebView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr), BaseWebViewClient.WebviewTouch {
private var webViewCallBack: WebViewCallBack? = null
private var mHeaders: HashMap<String, String> ?= null
private var isTouchByUser: Boolean = false
private var redirectIntercept = false // 是否拦截重定向
protected var mContext: Context? = null

init {
init(context)
}
fun registerWebViewCallBack(webViewCallBack: WebViewCallBack) {
this.webViewCallBack = webViewCallBack
webViewClient = BaseWebViewClient(this, webViewCallBack, mHeaders, this, redirectIntercept)
}

fun setHeaders(mHeaders: HashMap<String, String>) {
this.mHeaders = mHeaders
}

fun setRedirectIntercept(intercept: Boolean){
redirectIntercept = intercept
}

protected fun init(context: Context?) {
mContext = context
// 初始化默认设置
WebviewDefaultSetting.getInstance().toSetting(this)
// javascript调用接口
addJavascriptInterface(this, "webview")
// web进程初始化主进程调用接口
CommandDispatcher.getInstance().initAidlConnect(getContext())
}


@JavascriptInterface
fun post(cmd: String, param: String) {
// web进程的主线程执行
Handler().post {
try {
if (webViewCallBack != null) {
CommandDispatcher.getInstance().execBySelf(context, cmd, param, this@BaseWebView)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}

override fun loadUrl(url: String) {
if (mHeaders == null) {
super.loadUrl(url)
} else {
super.loadUrl(url, mHeaders)
}
Log.e(TAG, "load url: $url")
resetAllStateInternal(url)
}

/**
* 处理header数据请求
* @param url String
* @param additionalHttpHeaders Map<String?, String?>
*/
override fun loadUrl(url: String, additionalHttpHeaders: Map<String?, String?>?) {
super.loadUrl(url, additionalHttpHeaders)
Log.e(TAG, "load url: $url")
resetAllStateInternal(url)
}

/**
* 回调js的callback方法
* @param response String?
*/
fun handleCallback(response: String?) {
if (!TextUtils.isEmpty(response)) {
val trigger = "javascript:callback($response)"
evaluateJavascript(trigger, null)
}
}

/**
* 回调js的cmd方法
* @param cmd String
* @param param Any?
*/
fun loadJS(cmd: String, param: Any?) {
val trigger = "javascript:" + cmd + "(" + Gson().toJson(param) + ")"
evaluateJavascript(trigger, null)
}

/**
* 回调js的dispatchEvent方法做事件分发
* @param name String
*/
fun dispatchEvent(name: String) {
val param = HashMap<String,String>(1)
param["name"] = name
loadJS("dispatchEvent", param)
}

private fun resetAllStateInternal(url: String) {
// url为空或者回调js方法直接返回
if (!TextUtils.isEmpty(url) && url.startsWith("javascript:")) {
return
}
resetAllState()
}

// 加载url时重置touch状态
protected fun resetAllState() {
isTouchByUser = false
}

override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> isTouchByUser = true
}
return super.onTouchEvent(event)
}

companion object {
private const val TAG = "BaseWebView"
const val CONTENT_SCHEME = "file:///android_asset/"
}

override fun isTouchByUser(): Boolean {
return isTouchByUser
}
}

封装WebViewClient

回调接口逻辑,url跳转判断,重定向处理,对特殊链接统一处理,刷新处理,拦截指定url处理,判断是否加载完成,封装请求头,ssl错误处理

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
class BaseWebViewClient(
val webView: WebView, // 封装的回调
val webViewCallBack: WebViewCallBack, // 封装的回调
val mHeaders: HashMap<String, String>?, // 请求头
val mWebviewTouch: WebviewTouch, // 是否有点击操作
val redirectIntercept: Boolean // 是否拦截重定向
) : WebViewClient() {
var isReady = false // 是否加载完成

interface WebviewTouch {
fun isTouchByUser(): Boolean
}

/**
* url重定向会执行此方法以及点击页面某些链接也会执行此方法
*
* @return true:表示当前url已经加载完成,即使url还会重定向都不会再进行加载 false 表示此url默认由系统处理,该重定向还是重定向,直到加载完成
*/
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
Log.e(TAG, "shouldOverrideUrlLoading url: $url")
// 未发生过点击,当前是重定向且不拦截重定向
if (!mWebviewTouch.isTouchByUser() && !redirectIntercept) {
return super.shouldOverrideUrlLoading(view, url)
}
// 如果链接跟当前链接一样,表示刷新
if (webView.url == url) {
return super.shouldOverrideUrlLoading(view, url)
}
// 特殊链接处理,跳转手机自带应用
if (handleLinked(url)) {
return true
}
// 拦截指定url
if (webViewCallBack.overrideUrlLoading(url)) {
return true
}
// 控制页面中点开新的链接在当前webView中打开
view.loadUrl(url, mHeaders)
return true
}

@RequiresApi(api = Build.VERSION_CODES.N)
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
Log.e(TAG, "shouldOverrideUrlLoading url: " + request.url)
// 当前链接的重定向
if (!mWebviewTouch.isTouchByUser() && !redirectIntercept) {
return super.shouldOverrideUrlLoading(view, request)
}
// 如果链接跟当前链接一样,表示刷新
if (webView.url == request.url.toString()) {
return super.shouldOverrideUrlLoading(view, request)
}
// 需要跳转应用的特殊链接
if (handleLinked(request.url.toString())) {
return true
}
// 拦截指定url
if (webViewCallBack.overrideUrlLoading(request.url.toString())) {
return true
}
// 控制页面中点开新的链接在当前webView中打开
view.loadUrl(request.url.toString(), mHeaders)
return true
}

/**
* 支持电话、短信、邮件、地图跳转,跳转的都是手机系统自带的应用
*/
private fun handleLinked(url: String): Boolean {
if (url.startsWith(WebView.SCHEME_TEL)
|| url.startsWith(SCHEME_SMS)
|| url.startsWith(WebView.SCHEME_MAILTO)
|| url.startsWith(WebView.SCHEME_GEO)
) {
try {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
webView.context.startActivity(intent)
} catch (ignored: ActivityNotFoundException) {
ignored.printStackTrace()
}
return true
}
return false
}

override fun onPageFinished(view: WebView?, url: String?) {
Log.e(TAG, "onPageFinished url:$url")
if (url == null) return
if (!TextUtils.isEmpty(url) && url.startsWith(CONTENT_SCHEME)) {
isReady = true
}
webViewCallBack.pageFinished(url)
}

override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
Log.e(TAG, "onPageStarted url: $url")
if (url == null) return
webViewCallBack.pageStarted(url)
}


override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) {
super.onScaleChanged(view, oldScale, newScale)
}

@TargetApi(21)
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return shouldInterceptRequest(view, request.url.toString())
}

/**
* 默认继续加载
* @param view WebView
* @param url String
* @return WebResourceResponse?
*/
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
return null
}

/**
* webview加载错误处理
* @param view WebView
* @param errorCode Int
* @param description String
* @param failingUrl String
*/
override fun onReceivedError(
view: WebView,
errorCode: Int,
description: String,
failingUrl: String
) {
super.onReceivedError(view, errorCode, description, failingUrl)
Log.e(
TAG,
"webview error$errorCode + $description"
)
webViewCallBack.onError(errorCode,description,failingUrl)
}

/**
* SSL错误处理
* @param view
* @param handler
* @param error
*/
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
var message = webView.context.getString(R.string.ssl_error)
when (error.primaryError) {
SslError.SSL_UNTRUSTED -> message =
webView.context.getString(R.string.ssl_error_not_trust)
SslError.SSL_EXPIRED -> message = webView.context.getString(R.string.ssl_error_expired)
SslError.SSL_IDMISMATCH -> message =
webView.context.getString(R.string.ssl_error_mismatch)
SslError.SSL_NOTYETVALID -> message =
webView.context.getString(R.string.ssl_error_not_valid)
}
message += webView.context.getString(R.string.ssl_error_continue_open)
Log.v(TAG,message)
}

companion object {
private const val TAG = "WebviewClient"
const val SCHEME_SMS = "sms:"
}
}

封装WebChromeClient

处理文件选择和相册选择回调,js提示回调,进度刷新回调

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
class BaseWebChromeClient(private val progressHandler: Handler) : WebChromeClient() {
private var mFilePathCallback: ValueCallback<Array<Uri>>? = null // 文件选择回调
private var mCameraPhotoPath: String? = null // 相册选择回调
override fun onReceivedTitle(view: WebView, title: String) {
super.onReceivedTitle(view, title)
if (view is ProgressWebView) {
if (!TextUtils.isEmpty(title)) {
val params = ArrayMap<String, String>()
params[WebConstants.COMMAND_UPDATE_TITLE_PARAMS] = title
// 调起刷新标题命令
(view as BaseWebView).post(WebConstants.COMMAND_UPDATE_TITLE, Gson().toJson(params))
}
}
}

// 进度更新回调
override fun onProgressChanged(view: WebView, newProgress: Int) {
var newProgress = newProgress
val message = Message()
if (newProgress == 100) {
message.obj = newProgress
progressHandler.sendMessageDelayed(message, 200)
} else {
if (newProgress < 10) {
newProgress = 10
}
message.obj = newProgress
progressHandler.sendMessage(message)
}
super.onProgressChanged(view, newProgress)
}

// js 提示回调
override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean {
AlertDialog.Builder(view.context)
.setTitle(R.string.dialog_alert_title)
.setMessage(message)
.setPositiveButton(
R.string.ok
) { dialoginterface, i -> //按钮事件
Toast.makeText(
view.context,
view.context.getString(R.string.ok) + " clicked.",
Toast.LENGTH_LONG
).show()
}.show()
//result.confirm();// 不加这行代码,会造成Alert劫持:Alert只会弹出一次,并且WebView会卡死
return true
}

//文件选择回调
override fun onShowFileChooser(
webView: WebView,
filePathCallback: ValueCallback<Array<Uri>>,
fileChooserParams: FileChooserParams
): Boolean {
mFilePathCallback?.onReceiveValue(null)
mFilePathCallback = filePathCallback
var takePictureIntent:Intent? = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (takePictureIntent?.resolveActivity(webView.context.packageManager) != null) {
// Create the File where the photo should go
var photoFile: File? = null
try {
photoFile = createImageFile()
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath)
} catch (ex: IOException) {
ex.printStackTrace()
}

// Continue only if the File was successfully created
if (photoFile != null) {
mCameraPhotoPath = "file:" + photoFile.absolutePath
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile))
} else {
takePictureIntent = null
}
}
if (takePictureIntent != null && mFilePathCallback != null){
(webView as BaseWebView).webViewCallBack?.onShowFileChooser(takePictureIntent, mFilePathCallback!!)
}
return true
}

/**
* More info this method can be found at
* http://developer.android.com/training/camera/photobasics.html
*
* @return
* @throws IOException
*/
@Throws(IOException::class)
private fun createImageFile(): File {
// Create an image file name
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val imageFileName = "JPEG_" + timeStamp + "_"
val storageDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
return File.createTempFile(imageFileName, ".jpg", storageDir)
}
}

封装WebView回调接口

1
2
3
4
5
6
7
8
9
10
11
interface WebViewCallBack {
fun pageStarted(url: String) // 页面开始加载
fun pageFinished(url: String) // 页面加载完成
fun overrideUrlLoading(url: String): Boolean // 拦截url
fun onError(errorCode: Int,description: String,failingUrl:String) // 错误回调
// 执行操作
fun onShowFileChooser(
cameraIntent: Intent,
filePathCallback: ValueCallback<Array<Uri>>
)
}

封装进度条

大部分app展示web页面顶部有个web页的加载进度条提升交互体验

定义进度条操作接口

1
2
3
4
5
6
interface BaseProgressSpec {
fun show()
fun hide()
fun reset()
fun setProgress(newProgress: Int)
}

封装进度条控制逻辑

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 IndicatorHandler{
var baseProgressSpec: BaseProgressSpec? = null // 进度条接口

/**
* 单例
*/
companion object{
@Volatile private var instance: IndicatorHandler? = null
fun getInstance() = instance?: synchronized(this){
instance?: IndicatorHandler().also { instance = it }
}
}

/**
* 进度刷新
* @param newProgress Int
*/
fun progress(newProgress: Int) {
when (newProgress) {
0 -> {
reset()
}
in 1..10 -> {
showProgressBar()
}
in 11..94 -> {
setProgressBar(newProgress)
}
else -> {
setProgressBar(newProgress)
finish()
}
}
}


fun reset() {
baseProgressSpec?.reset()
}

fun finish() {
baseProgressSpec?.hide()
}

fun setProgressBar(n: Int) {
baseProgressSpec?.setProgress(n)
}

fun showProgressBar() {
baseProgressSpec?.show()
}

fun inJectProgressView(baseProgressSpec: BaseProgressSpec?):IndicatorHandler {
this.baseProgressSpec = baseProgressSpec
return this
}
}

自定义进度条

自定义FrameLayout布局,实现进度条操作接口,进度小于95时渲染匀速动画,大于等于95时渲染透明加减速动画

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
class WebProgressBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),BaseProgressSpec {
/**
* 进度条颜色
*/
private var mColor = 0

/**
* 进度条的画笔
*/
private var mPaint: Paint = Paint()

/**
* 进度条动画
*/
private var mAnimator: Animator? = null

/**
* 控件的宽度
*/
private var mTargetWidth = 0

companion object{
/**
* 默认匀速动画最大的时长
*/
const val MAX_UNIFORM_SPEED_DURATION = 5 * 1000

/**
* 默认加速后减速动画最大时长
*/
const val MAX_DECELERATE_SPEED_DURATION = 600

/**
* 结束动画时长 , Fade out 。
*/
const val DO_END_ANIMATION_DURATION = 300

/**
* 当前匀速动画最大的时长
*/
var CURRENT_MAX_UNIFORM_SPEED_DURATION = MAX_UNIFORM_SPEED_DURATION

/**
* 当前加速后减速动画最大时长
*/
var CURRENT_MAX_DECELERATE_SPEED_DURATION = MAX_DECELERATE_SPEED_DURATION

/**
* 标志当前进度条的状态
*/
private var TAG = 0
const val UN_START = 0
const val STARTED = 1
const val FINISH = 2

/**
* 默认的高度
*/
var WEB_PROGRESS_DEFAULT_HEIGHT = 3
}

init {
init(context)
}

private fun init(context: Context) {
mColor = Color.parseColor("#c15d3e")
mPaint.isAntiAlias = true
mPaint.color = mColor
mPaint.isDither = true
mPaint.strokeCap = Paint.Cap.SQUARE
mTargetWidth = context.resources.displayMetrics.widthPixels
WEB_PROGRESS_DEFAULT_HEIGHT = dipToPx(context, 2.5f)
}

private fun setColor(color: Int) {
mColor = color
mPaint.color = color
}

fun setColor(color: String) {
this.setColor(Color.parseColor(color))
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val wMode = MeasureSpec.getMode(widthMeasureSpec)
val hMode = MeasureSpec.getMode(heightMeasureSpec)
var w = MeasureSpec.getSize(widthMeasureSpec)
var h = MeasureSpec.getSize(heightMeasureSpec)
if (wMode == MeasureSpec.AT_MOST) {
w =
if (w <= context.resources.displayMetrics.widthPixels) w else context.resources.displayMetrics.widthPixels
}
if (hMode == MeasureSpec.AT_MOST) {
h = WEB_PROGRESS_DEFAULT_HEIGHT
}
setMeasuredDimension(w, h)
}

private var currentProgress = 0f

override fun onDraw(canvas: Canvas?) {}

override fun dispatchDraw(canvas: Canvas) {
canvas.drawRect(
0f,
0f,
currentProgress / 100 * java.lang.Float.valueOf(this.width.toFloat()),
this.height.toFloat(),
mPaint
)
}

override fun show() {
if (visibility == GONE) {
this.visibility = VISIBLE
currentProgress = 0f
startAnim(false)
}
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mTargetWidth = measuredWidth
val screenWidth = context.resources.displayMetrics.widthPixels
if (mTargetWidth >= screenWidth) {
CURRENT_MAX_DECELERATE_SPEED_DURATION = MAX_DECELERATE_SPEED_DURATION
CURRENT_MAX_UNIFORM_SPEED_DURATION = MAX_UNIFORM_SPEED_DURATION
} else {
//取比值
val rate = mTargetWidth / java.lang.Float.valueOf(screenWidth.toFloat())
CURRENT_MAX_UNIFORM_SPEED_DURATION = (MAX_UNIFORM_SPEED_DURATION * rate).toInt()
CURRENT_MAX_DECELERATE_SPEED_DURATION = (MAX_DECELERATE_SPEED_DURATION * rate).toInt()
}
}

fun setProgress(progress: Float) {
if (visibility == GONE) {
visibility = VISIBLE
}
if (progress < 95f) return
if (TAG != FINISH) {
startAnim(true)
}
}

override fun hide() {
TAG = FINISH
}


private var target = 0f


/**
* 开始动画
* @param isFinished Boolean
*/
private fun startAnim(isFinished: Boolean) {
val v: Float = if (isFinished) 100F else 95.toFloat()
if (mAnimator?.isStarted == true) {
mAnimator?.cancel()
}
// 刷新当前进度
currentProgress = if (currentProgress == 0f) 0.00000001f else currentProgress
// 进度为0-94,匀速动画
if (!isFinished) {
val mAnimator = ValueAnimator.ofFloat(currentProgress, v)
val residue = 1f - currentProgress / 100 - 0.05f
// 使用匀速插值器
mAnimator.interpolator = LinearInterpolator()
mAnimator.duration = (residue * CURRENT_MAX_UNIFORM_SPEED_DURATION).toLong()
mAnimator.addUpdateListener(mAnimatorUpdateListener)
mAnimator.start()
this.mAnimator = mAnimator
}
// 进度大于等于95后执行透明减速的动画
else {
var segment95Animator: ValueAnimator? = null
// 使用减速插值器
if (currentProgress < 95f) {
segment95Animator = ValueAnimator.ofFloat(currentProgress, 95f)
val residue = 1f - currentProgress / 100f - 0.05f
segment95Animator.duration =
(residue * CURRENT_MAX_DECELERATE_SPEED_DURATION).toLong()
segment95Animator.interpolator = DecelerateInterpolator()
segment95Animator.addUpdateListener(mAnimatorUpdateListener)
}
// alpha动画
val mObjectAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f)
mObjectAnimator.duration = DO_END_ANIMATION_DURATION.toLong()
val mValueAnimatorEnd = ValueAnimator.ofFloat(95f, 100f)
mValueAnimatorEnd.duration = DO_END_ANIMATION_DURATION.toLong()
mValueAnimatorEnd.addUpdateListener(mAnimatorUpdateListener)
var mAnimatorSet = AnimatorSet()
mAnimatorSet.playTogether(mObjectAnimator, mValueAnimatorEnd)
if (segment95Animator != null) {
val mAnimatorSet1 = AnimatorSet()
// 执行alpha动画并刷新进度,后执行减速动画
mAnimatorSet1.play(mAnimatorSet).after(segment95Animator)
mAnimatorSet = mAnimatorSet1
}
mAnimatorSet.addListener(mAnimatorListenerAdapter)
mAnimatorSet.start()
mAnimator = mAnimatorSet
}
TAG = STARTED
target = v
}

// 动画刷新回调,刷新进度
private val mAnimatorUpdateListener =
ValueAnimator.AnimatorUpdateListener { animation ->
val t = animation.animatedValue as Float
currentProgress = t
this@WebProgressBar.invalidate()
}

// 监听动画完成
private val mAnimatorListenerAdapter: AnimatorListenerAdapter =
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
doEnd()
}
}

// 释放动画资源
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
/**
* animator cause leak , if not cancel;
*/
if (mAnimator?.isStarted == true) {
mAnimator?.cancel()
mAnimator = null
}
}

// 进度条执行完处理
private fun doEnd() {
if (TAG == FINISH && currentProgress == 100f) {
visibility = GONE
currentProgress = 0f
this.alpha = 1f
}
TAG = UN_START
}

// 状态重置
override fun reset() {
currentProgress = 0f
if (mAnimator?.isStarted == true) mAnimator?.cancel()
}

override fun setProgress(newProgress: Int) {
setProgress(newProgress.toFloat())
}
}

封装带进度条的WebView

设置自定义进度条,添加到WebView,传入主线程handler调用刷新进度条逻辑

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
class ProgressWebView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : BaseWebView(context,attrs,defStyleAttr) {
private var indicatorHandler: IndicatorHandler? = null
private var progressBar: WebProgressBar = WebProgressBar(context)
// 主线程调刷新进度条逻辑
private val mHandler: Handler = object :Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
val progress = msg.obj as Int
indicatorHandler?.progress(progress)
}
}

init {
init()
}

private fun init() {
progressBar.layoutParams = LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
progressBar.visibility = GONE
addView(progressBar)
indicatorHandler = IndicatorHandler.getInstance().inJectProgressView(progressBar)
webChromeClient = BaseWebChromeClient(mHandler)
}
}

封装带WebView的Fragment

  • fragment基类,统一设置标题

    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
    open class BaseFragment: Fragment() {
    protected var mContext: Context? = null

    fun setTitle(titleId: Int) {
    activity?.setTitle(titleId)
    }

    fun setTitle(title: CharSequence?) {
    activity?.title = title
    }

    override fun onAttach(context: Context) {
    super.onAttach(context)
    this.mContext = context
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    if (mContext == null){
    mContext = context
    }
    }

    override fun getContext(): Context? {
    return if (super.getContext() == null) mContext else super.getContext()
    }
    }
  • 封装带Webview的fragment基类,处理带header请求,定义页面开始加载,结束加载,指定url拦截,加载错误回调,是否重定向拦截,网页返回按键处理,相册文件选择回调处理,页面销毁时释放WebView

    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
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    abstract class BaseWebviewFragment : BaseFragment(), WebViewCallBack {
    companion object {
    const val INFO_HEADERS = "info_headers"
    const val REDIRECT_INTERCEPT = "redirect_intercept"
    const val REQUEST_CODE = 1
    }

    var webView: BaseWebView? = null
    protected var headers: HashMap<String, String> = HashMap()
    var webUrl: String? = null
    var pageStarted: ((String) -> Unit)? = null
    var pageFinished: ((String) -> Unit)? = null
    var overrideUrlLoading: ((String) -> Boolean)? = null
    var onError: ((Int, String, String) -> Unit)? = null
    var onShowFileChooser: ((Intent, ValueCallback<Array<Uri>>) -> Unit)? = null
    private var redirectIntercept = false
    @LayoutRes
    protected abstract fun getLayoutRes(): Int

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val bundle = arguments
    if (bundle != null) {
    webUrl = bundle.getString(WebConstants.INTENT_TAG_URL)
    if (bundle.containsKey(INFO_HEADERS)) {
    headers =
    bundle.getSerializable(INFO_HEADERS) as HashMap<String, String>
    redirectIntercept = bundle.getBoolean(REDIRECT_INTERCEPT)
    }
    }
    // 注册吐司命令
    CommandsManager.getInstance().registerCommand(ToastCommand())
    }

    override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
    ): View? {
    val view = inflater.inflate(getLayoutRes(), container, false)
    webView = view.findViewById(R.id.web_view)
    webView?.setHeaders(headers)
    webView?.setRedirectIntercept(redirectIntercept)
    return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    webView?.registerWebViewCallBack(this)
    loadUrl()
    }

    protected open fun loadUrl() {
    webUrl?.let {
    webView?.loadUrl(it)
    }
    }

    override fun onResume() {
    super.onResume()
    Log.v("BaseWebviewFragment","onResume")
    webView?.dispatchEvent("pageResume")
    webView?.onResume()
    }

    override fun onPause() {
    super.onPause()
    Log.v("BaseWebviewFragment","onPause")
    webView?.dispatchEvent("pagePause")
    webView?.onPause()
    }

    override fun onStop() {
    super.onStop()
    webView?.dispatchEvent("pageStop")
    }

    override fun onDestroyView() {
    super.onDestroyView()
    webView?.dispatchEvent("pageDestroy")
    clearWebView(webView)
    }

    open fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
    return if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_DOWN) {
    onBackHandle()
    } else false
    }


    override fun pageStarted(url: String) {
    this.pageStarted?.invoke(url)
    }

    override fun pageFinished(url: String) {
    this.pageFinished?.invoke(url)
    }

    override fun overrideUrlLoading(url: String): Boolean {
    this.overrideUrlLoading?.let {
    return it.invoke(url)
    }
    return false
    }

    override fun onError(errorCode: Int, description: String, failingUrl: String) {
    onError?.invoke(errorCode, description, failingUrl)
    }

    /**
    * 处理返回
    * @return Boolean
    */
    protected open fun onBackHandle(): Boolean {
    return if (webView != null) {
    if (webView!!.canGoBack()) {
    webView?.goBack()
    true
    } else {
    false
    }
    } else false
    }

    private fun clearWebView(m: WebView?) {
    val m: WebView? = m ?: return
    // 非主线程退出
    if (Looper.myLooper() != Looper.getMainLooper()) return
    // 停止加载处理
    m?.stopLoading()
    if (m?.handler != null) {
    m.handler.removeCallbacksAndMessages(null)
    }
    // 移除webview的所有view
    m?.removeAllViews()
    // 获取父布局移除webview
    val mViewGroup: ViewGroup? = m?.parent as? ViewGroup
    mViewGroup?.removeView(m)
    // 回调置空
    m?.webChromeClient = null
    m?.webViewClient = null
    m?.tag = null
    // 清理历史并销毁
    m?.clearHistory()
    m?.destroy()
    }

    override fun onShowFileChooser(
    cameraIntent: Intent,
    filePathCallback: ValueCallback<Array<Uri>>
    ) {
    if (onShowFileChooser != null) {
    onShowFileChooser!!.invoke(cameraIntent, filePathCallback)
    } else {
    mFilePathCallback = filePathCallback
    //------------------------------------
    //弹出选择框有:相机、相册(Android9.0,Android8.0)
    //如果是小米Android6.0系统上,依然是:相机、相册、文件管理
    //如果安装了其他的相机(百度魔拍)、文件管理程序(ES文件管理器),也有可能会弹出
    val selectionIntent = Intent(Intent.ACTION_PICK, null)
    selectionIntent.type = "image/*"
    //------------------------------------
    val intentArray: Array<Intent?> = arrayOf(cameraIntent)
    val chooserIntent = Intent(Intent.ACTION_CHOOSER)
    chooserIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.file_chooser))
    chooserIntent.putExtra(Intent.EXTRA_INTENT, selectionIntent)
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
    startActivityForResult(chooserIntent, REQUEST_CODE)
    }
    }

    private var mFilePathCallback: ValueCallback<Array<Uri>>? = null
    private val mCameraPhotoPath: String? = null

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) {
    REQUEST_CODE -> {
    var results: Array<Uri>? = null
    if (resultCode == Activity.RESULT_OK) {
    if (data == null) {
    if (mCameraPhotoPath != null) {
    Log.d("AppChooserFragment", mCameraPhotoPath)
    results = arrayOf(Uri.parse(mCameraPhotoPath))
    }
    } else {
    val dataString = data.dataString
    if (dataString != null) {
    results = arrayOf(Uri.parse(dataString))
    }
    }
    }
    mFilePathCallback?.onReceiveValue(results)
    mFilePathCallback = null
    }
    }
    }
    }
  • WebViewFragment实现类,header传参,是否拦截重定向,是否同步header到cookie

    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
    class WebviewFragment : BaseWebviewFragment() {
    override fun getLayoutRes(): Int {
    return R.layout.fragment_common_webview
    }

    companion object{
    fun newInstance(keyUrl: String, headers: HashMap<String, String> = HashMap(), redirectIntercept: Boolean,isSyncToCookie: Boolean): WebviewFragment? {
    val fragment = WebviewFragment()
    fragment.arguments = getBundle(keyUrl, headers, redirectIntercept)
    if (isSyncToCookie) {
    syncCookie(keyUrl, headers)
    }
    return fragment
    }

    private fun getBundle(url: String, headers: HashMap<String, String>, redirectIntercept:Boolean): Bundle? {
    val bundle = Bundle()
    bundle.putString(WebConstants.INTENT_TAG_URL, url)
    bundle.putSerializable(INFO_HEADERS, headers)
    bundle.putBoolean(REDIRECT_INTERCEPT, redirectIntercept)
    return bundle
    }


    /**
    * cookie同步到WebView
    *
    * @param url WebView要加载的url
    * @return true 同步cookie成功,false同步cookie失败
    * @Author JPH
    */
    private fun syncCookie(url: String?, map: Map<String, String?>): Boolean {
    val cookieManager = CookieManager.getInstance()
    for (key in map.keys) {
    cookieManager.setCookie(url, key + "=" + map[key])
    }
    val newCookie = cookieManager.getCookie(url)
    return !TextUtils.isEmpty(newCookie)
    }
    }

    }

    封装带WebView的Activity

    支持自定义标题布局,设置带WebView的Fragment并添加到Activity,按键处理,注册使用网页标题事件,网页Activity关闭事件

    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
    open class WebActivity : AppCompatActivity() {
    private var title: String? = null // 标题设置

    protected var webviewFragment: BaseWebviewFragment? = null
    // 标题布局可自定义
    @LayoutRes
    protected fun getLayoutTitle(): Int = R.layout.title_normal

    // 添加标题布局
    val titleView by lazy { LayoutInflater.from(this).inflate(getLayoutTitle(), fl_title) }

    companion object{
    // 启动入口
    fun startCommonWeb(context: Context, title: String?, url: String?,header: HashMap<String, String> = HashMap()) {
    val intent = Intent(context, WebActivity::class.java)
    intent.putExtra(WebConstants.INTENT_TAG_TITLE, title)
    intent.putExtra(WebConstants.INTENT_TAG_URL, url)
    intent.putExtra(WebConstants.INTENT_TAG_HEADERS, header)
    if (context is Service) {
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    }
    context.startActivity(intent)
    }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    CommandsManager.getInstance().registerCommand(titleUpdateCommand)
    setContentView(R.layout.activity_common_web)
    // 注册标题刷新回调
    // 设置标题和url
    title = intent.getStringExtra(WebConstants.INTENT_TAG_TITLE)
    val url = intent.getStringExtra(WebConstants.INTENT_TAG_URL)?:""

    // 如果是默认布局设置标题
    if (getLayoutTitle() == R.layout.title_normal){
    val textView = titleView.findViewById(R.id.tv_title) as TextView
    textView.text = title
    }

    // 填充webviewFragment
    val fm = supportFragmentManager
    val transaction = fm.beginTransaction()

    webviewFragment = null
    val params = intent.extras?.getSerializable(WebConstants.INTENT_TAG_HEADERS) as HashMap<String, String>
    webviewFragment = WebviewFragment.newInstance(
    url,
    params,
    redirectIntercept = false,
    isSyncToCookie = true
    )
    transaction.replace(R.id.web_view_fragment, webviewFragment as Fragment).commit()
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // 调用webviewFragment的按键回调
    if (webviewFragment != null && webviewFragment is BaseWebviewFragment && event != null) {
    val flag: Boolean = webviewFragment!!.onKeyDown(keyCode, event)
    if (flag) {
    return flag
    }
    }
    return super.onKeyDown(keyCode, event)
    }

    /**
    * 标题刷新命令
    */
    private val titleUpdateCommand: Command = object : Command {
    override fun name(): String {
    return WebConstants.COMMAND_UPDATE_TITLE
    }

    override fun exec(context: Context, params: ArrayMap<String, String>, resultBack: ResultBack) {
    if (params.containsKey(WebConstants.COMMAND_UPDATE_TITLE_PARAMS)) {
    if (getLayoutTitle() == R.layout.title_normal){
    val textView = titleView.findViewById(R.id.tv_title) as TextView
    textView.text = params[WebConstants.COMMAND_UPDATE_TITLE_PARAMS]
    }
    }
    }
    }

    /**
    * 默认title布局返回 回调
    * @param view View
    */
    fun back(view: View){
    finish()
    }

    override fun finish() {
    val map = ArrayMap<String, String>().apply {
    this[WebFinishCommand.FINISH_COMMAND_PARAMS] = "true"
    }
    CommandDispatcher.getInstance().execByOther(WebFinishCommand.FINISH_COMMAND, Gson().toJson(map))
    super.finish()
    }

    /**
    * 点击右侧button 回调
    * @param view View
    */
    fun clickRight(view: View){

    }
    }

    分发消息

  • 命令模式封装消息管理器,对当前分发的消息采用当前进程自行处理,或跨进程处理,根据传参判断是否需要回调给h5

1
2
3
4
5
6
7
8
9
10
11
/**
* 命令实现接口
*/
interface Command {


// 命令名称
fun name():String
// 执行命令传参和定义返回接口
fun exec(context: Context, map: ArrayMap<String, String>, resultBack: ResultBack)
}
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
/**
* 命令管理器
* @property instance CommandsManager?
* @property commands ArrayMap<String, Command>
*/
class CommandsManager private constructor(){
private var instance: CommandsManager? = null
// 命令缓存
val commands:ArrayMap<String, Command> = ArrayMap()

// 返回单例
companion object{
@Volatile private var instance:CommandsManager? = null
fun getInstance() = instance?: synchronized(this){
instance?:CommandsManager().also { instance = it }
}
}
/**
* 注册web进程处理的命令
* @param command Command
*/
fun registerCommand(command: Command) {
commands[command.name()] = command
}


/**
* 非UI线程执行
*/
fun execCommand(
context: Context,
action: String,
params: ArrayMap<String, String>?,
resultBack: ResultBack
) {
// 命令/传参不为空,执行命令逻辑
if (commands[action] != null && params != null) {
commands[action]?.exec(context, params, resultBack)
}
// 返回错误
else {
val aidlError =
AidlError(WebConstants.NO_METHOD, WebConstants.NO_METHOD_STR)
resultBack.onResult(WebConstants.FAILED, action, aidlError)
}
}


/**
* 判断命令是否存在
* @param action String?
* @return Boolean
*/
fun isCommandExist(action: String?): Boolean {
return commands[action] != 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
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
class CommandDispatcher {
/**
* json解析对象
*/
private val gson by lazy { Gson() }

// 跨进程通信接口
protected var iHandleAction: IHandleAction? = null


/**
* 单例返回
*/
companion object {
@Volatile
private var instance: CommandDispatcher? = null

fun getInstance() = instance ?: synchronized(this) {
return instance ?: CommandDispatcher().also { instance = it }
}
}

/**
* 跨进程连接
* @param context Context?
*/
fun initAidlConnect(context: Context) {
// 避免重复初始化
if (iHandleAction != null) {
return
}
// 开启子线程,获取跨进程调用实例
Thread {
iHandleAction = IHandleAction.Stub.asInterface(
ProcessConnector.getInstance(context).getAidlInterface()
)
}.start()
}

/** 其它进程执行命令
* @param cmd String
* @param params String
*/
fun execByOther(cmd: String, params: String) {
Log.i("CommandDispatcher", "${Process.myPid()}进程调跨进程处理,command: $cmd params: $params")
try {
iHandleAction?.handleAction(cmd, params, object : ICallback.Stub() {
override fun onResult(
responseCode: Int,
actionName: String?,
response: String?
) {
// 当前进程回调结果
handleCallback(responseCode, actionName, response)
}
})
} catch (e: Exception) {
Log.e("CommandDispatcher", "Command exec error!!!!", e)
}
}

/**
* 当前进程执行命令
* @param context Context
* @param cmd String
* @param params String
* @param webView WebView
*/
fun execBySelf(context: Context, cmd: String, params: String, webView: WebView? = null) {
Log.i("CommandDispatcher", "${Process.myPid()}进程自己处理,command: $cmd params: $params")
try {
// json传参数据反序列化为map对象
val mapParams = gson.fromJson<ArrayMap<String, String>>(
params,
ArrayMap::class.java
)
// 命令管理器执行该命令逻辑
CommandsManager.getInstance()
.execCommand(context, cmd, mapParams, object : ResultBack {
// 结果回调
override fun onResult(status: Int, action: String, result: Any?) {
handleCallback(status, action, gson.toJson(result), webView)
}
})
} catch (e: Exception) {
Log.e("CommandDispatcher", "Command exec error!!!!", e)
}
}

private fun handleCallback(
responseCode: Int, actionName: String?, response: String?,
webView: WebView? = null
) {
Log.d(
"CommandDispatcher",
String.format(
"Callback result: responseCode= %s, action= %s, result= %s",
responseCode,
actionName,
response
)
)
runOnUiThread(Runnable {
// 返回结果反序列化为json
val params = Gson().fromJson<ArrayMap<String, String>>(response, ArrayMap::class.java)
// 从传参判断是否需要回调给h5
if (webView != null && params[WebConstants.NATIVE2WEB_CALLBACK] != null && !TextUtils.isEmpty(
params[WebConstants.NATIVE2WEB_CALLBACK].toString()
)
) {
// web进程调js
if (webView is BaseWebView) {
webView.handleCallback(response)
}
}
})
}
}
  • 进程连接管理类,绑定另一个服务进程,连接断开时解绑重连,返回跨进程通信接口
    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
    class ProcessConnector private constructor(context: Context) {
    private var mContext: Context = context.applicationContext
    // 跨进程通信接口
    private var iHandleAction: IHandleAction? = null
    private var mConnectBinderPoolCountDownLatch: CountDownLatch = CountDownLatch(1) // 个数为1的同步变量

    // 单例对象
    companion object {
    @Volatile
    private var instance: ProcessConnector? = null
    fun getInstance(context: Context) = instance ?: synchronized(this) {
    instance ?: ProcessConnector(context).also { instance = it }
    }
    }

    init {
    connectToOtherProcessService()
    }

    // 绑定另一个服务进程
    @Synchronized
    private fun connectToOtherProcessService() {
    val targetClass = if(!isMainProcess(mContext)) RemoteBindMainService::class.java else MainBindRemoteService::class.java
    // 绑定主进程服务
    mContext.bindService(Intent(mContext,targetClass), object : ServiceConnection {

    // 服务断开
    override fun onServiceDisconnected(name: ComponentName) {
    Log.v("ServiceConnect","跨进程连接断开")
    }

    // 服务连接
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
    Log.v("ServiceConnect","跨进程连接成功")
    // 绑定断开回调
    iHandleAction = IHandleAction.Stub.asInterface(service)
    try {
    iHandleAction?.asBinder()?.linkToDeath(object : IBinder.DeathRecipient {
    // 解绑重连
    override fun binderDied() {
    iHandleAction?.asBinder()?.unlinkToDeath(this, 0)
    iHandleAction = null
    connectToOtherProcessService()
    }
    }, 0)
    } catch (e: RemoteException) {
    e.printStackTrace()
    }
    mConnectBinderPoolCountDownLatch.countDown()
    }
    }, Context.BIND_AUTO_CREATE)

    // 线程同步阻塞
    try {
    mConnectBinderPoolCountDownLatch.await()
    } catch (e: InterruptedException) {
    e.printStackTrace()
    }
    }


    // 返回跨进程调用接口
    fun getAidlInterface(): IBinder? {
    return iHandleAction?.asBinder()
    }

    }

    跨进程通信

    跨进程接口定义

    定义AIDL跨进程调用接口
    1
    2
    3
    interface IHandleAction {
    void handleAction(String actionName, String jsonParams, in ICallback callback);
    }
    定义AIDL跨进程调用回调接口
    1
    2
    3
    interface ICallback {
    void onResult(int responseCode, String actionName, String response);
    }

    跨进程接口实现

    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
    class AidlInterface (val context: Context): IHandleAction.Stub() {
    private val gson: Gson by lazy { Gson() }
    override fun handleAction(
    actionName: String?,
    jsonParams: String?,
    callback: ICallback?
    ) {
    Log.v("AidlInterface","${Process.myPid()}进程正在跨进程执行命令$actionName")
    if (actionName != null){
    // 跨进程处理命令
    CommandsManager.getInstance().execCommand(context, actionName,
    gson.fromJson(jsonParams, ArrayMap::class.java) as? ArrayMap<String, String>, object : ResultBack {
    override fun onResult(status: Int, action: String, result: Any?) {
    try {
    // 原进程处理回调
    callback?.onResult(status, actionName, Gson().toJson(result))
    } catch (e: Exception) {
    e.printStackTrace()
    }
    }

    })
    }
    }

    }

    声明主进程服务和Web进程服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 主进程绑定web进程
    */
    class MainBindRemoteService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
    val pid = Process.myPid()
    Log.d(
    "MainBindRemoteService", String.format(
    "web进程: %s",
    "当前进程ID为:$pid---主进程连接web进程成功"
    )
    )
    // web进程操作对象
    return AidlInterface(this)
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * web绑定主进程服务
    */
    class RemoteBindMainService: Service() {
    override fun onBind(intent: Intent?): IBinder? {
    val pid = Process.myPid()
    Log.d(
    "RemoteBindMainService", String.format(
    "主进程: %s",
    "当前进程ID为:$pid----web连接主进程成功"
    )
    )
    // 主进程操作对象
    return AidlInterface(this)
    }
    }
    1
    2
    3
    <service android:name="com.example.weblib.service.MainBindRemoteService"
    android:process=":remoteweb"/>
    <service android:name="com.example.weblib.service.RemoteBindMainService" />
    最后利用ContentProvider初始化封装的SDK,兼容>=Android 9.0 不同进程中使用Webview需要配置不同的缓存目录
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class WebInitializer : ContentProvider() {
    override fun onCreate(): Boolean {
    Log.v("SdkInitializer","WebLib初始化进程:${getProcessName(context!!)}")
    if (context == null) return true
    // >=Android 9.0 在不同进程中使用Webview需要配置缓存目录,配置WebLib进程使用的Webview缓存目录
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
    WebView.setDataDirectorySuffix("remote")
    }
    return true
    }
    ...
    }
    1
    2
    3
    4
    5
    6
    <provider
    android:authorities="${applicationId}.library-installer"
    android:name="com.example.weblib.WebInitializer"
    android:multiprocess="true"
    android:process=":remoteweb"
    android:exported="false"/>