CustomWebView

JS interface와 동작을 정의한 커스텀 웹뷰 클래스

constant

private val TAG:String = CustomWebView::class.java.simpleName
private val mContext: Activity? = context as? Activity
private var androidBridge: AndroidBridge
private var commonWebviewCont: CommonWebViewConroller?

//scroll 은 각각의 화면별 함수형 리스너로 전달
var setScrollChangelistener: ((l: Int, t: Int, oldl: Int, oldt: Int) -> Unit)? = null

//image 연동 관련 <-> webview
var mUploadMessage: ValueCallback<Uri?>? = null
var mUploadMessage2: ValueCallback<Array<Uri?>>? = null
var mCM: String? = null

init

  • web Bridge 를 설정한다.

  • webview의 설정을 변경한다.

  • 소셜로그인 동작을 정의한다.

init {
    Log.i("CustomWebView","initialize")

    androidBridge = AndroidBridge()
    this.addJavascriptInterface(androidBridge, "AndroidBridge")
    this.webViewClient = WebClient()
    this.webChromeClient = ChroniumClient()

    //웹페이지 줌인/아웃 관련 설정
    this.settings?.javaScriptEnabled = true
    this.settings?.javaScriptCanOpenWindowsAutomatically = true
    this.settings?.setSupportMultipleWindows(true)
    this.settings?.domStorageEnabled = true
    this.settings?.allowFileAccess = true
    //webView.getSettings().setSupportZoom(true);
    //webView.getSettings().setBuiltInZoomControls(true);
    //webView.getSettings().setDisplayZoomControls(false);

    if (BuildConfig.DEBUG_MODE) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true)
        }
    }

    commonWebviewCont = mContext as? CommonWebViewConroller
    //facebook setting
    commonWebviewCont?.facebookLogin = FaceBookLogin(mContext)
    commonWebviewCont?.facebookLogin?.setOnListener(object : LoginInterface.FaceBookLoginInterface {
        override fun onComplete(id: String?, name: String?, email: String?, imagePath: String?, temp: String?) {

            SharedPreferenceManager.setJoinType(FACEBOOK_LOGIN_JOIN_TYPE)
            SharedPreferenceManager.setNickName(name?:"")
            SharedPreferenceManager.setEmailAddr(email?:"")
            SharedPreferenceManager.setProfileImg(imagePath?:"")

            val jsonParam:JsonObject = JsonObject()
            jsonParam.addProperty("type", FACEBOOK_LOGIN_JOIN_TYPE.toString())
            jsonParam.addProperty("email",email?:"")
            jsonParam.addProperty("name",name?:"")
            jsonParam.addProperty("imageURL",imagePath?:"")
            jsonParam.addProperty("snsId",id?:"")

            //TODO success 후 중복이나, error 상황이나 등등은 어찌 처리? session logout(토큰 만료) 처리 해야함?
            androidBridge.responseSuccessOrFail(COMMON_SUCCESS, jsonParam)
        }

        override fun onFail(type: Int) {
            if (type == 0) {
                Toast.makeText(
                    mContext,
                    "FaceBook 가입에 문제가 발생했습니다. 카카오톡, 이메일로 가입 부탁 드립니다.",
                    Toast.LENGTH_SHORT
                ).show()
            } else if (type == 1) {
                Toast.makeText(mContext, "Email 정보가 확인되지 않습니다. 카카오톡, 이메일로 가입 부탁 드립니다.", Toast.LENGTH_SHORT)
                    .show()
            }
            val jsonParam = JsonObject()
            androidBridge.responseSuccessOrFail(COMMON_FAIL, jsonParam)
        }

    })

    //kakao setting
    commonWebviewCont?.kakaoLogin = KaKaoLogin(mContext)
    commonWebviewCont?.kakaoLogin?.setOnListener(object : LoginInterface.KaKaoLoginInterface {
        override fun onComplete(id: String?, name: String?, email: String?, imagePath: String?, temp: String?) {

            //TODO 성공후 로직 확인
            SharedPreferenceManager.setJoinType(KAKAOTALK_LOGIN_JOIN_TYPE)
            SharedPreferenceManager.setNickName(name?:"")
            SharedPreferenceManager.setEmailAddr(email?:"")
            SharedPreferenceManager.setProfileImg(imagePath?:"")

            val jsonParam:JsonObject = JsonObject()
            jsonParam.addProperty("type",KAKAOTALK_LOGIN_JOIN_TYPE.toString())
            jsonParam.addProperty("email",email?:"")
            jsonParam.addProperty("name",name?:"")
            jsonParam.addProperty("imageURL",imagePath?:"")
            jsonParam.addProperty("snsId",id?:"")
            androidBridge.responseSuccessOrFail(COMMON_SUCCESS, jsonParam)

        }

        override fun onFail(type: Int) {
            Toast.makeText(mContext, "오류로 카카오로그인 실패하였습니다.", Toast.LENGTH_SHORT).show()
            val jsonParam = JsonObject()
            androidBridge.responseSuccessOrFail(COMMON_FAIL, jsonParam)
        }

    })

}

constructer

constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int): super(context, attrs, defStyle)

onScrollChanged

  • WebView의 스크롤이 되었을때의 동작을 정의한다.

override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
    super.onScrollChanged(l, t, oldl, oldt)
    //Log.i("customWebView", "$l, $t, $oldl, $oldt")
    this.setScrollChangelistener?.invoke(l, t, oldl, oldt)
}

AndroidBridge

  • JS interface 동작을 정의한다.

LocationListener

  • 서버에 위치 정보 값을 전달한다.

val mGetLocationAndResultSendListener : LocationListener by lazy {
    object : LocationListener {
        override fun onLocationChanged(location: Location?) {
            Log.i(TAG, "=== location : ${location?.latitude} / ${location?.longitude} ===")

            val jsonParams = JsonObject()
            jsonParams.addProperty("lat",location?.latitude)
            jsonParams.addProperty("lon",location?.longitude)

            responseSuccessOrFail(COMMON_SUCCESS, jsonParams)
        }
        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
        }
        override fun onProviderDisabled(provider: String?) {
        }
        override fun onProviderEnabled(provider: String?) {
        }
    }
}

goHome

  • 홈화면으로 돌아가는 동작을 정의한다.

@JavascriptInterface
fun goHome() {
    Logger.e(TAG, "goHome")
}

postMessage

  • 서버에 메시지를 전달한다.


@JavascriptInterface
fun postMessage(postMessage: String?) {
    /* ex
     {
      "name": "setTopMenu",
      "params": {
        "preBt": "Y",
        "title": "하이브리드 CallTest"
      },
      "success": "nativeCall.successCB",
      "fail": "nativeCall.failCB"
    }
     */

    postMessage?.let{
        Log.i(TAG, "postMessage:$postMessage")
        val parseMessage = Gson().fromJson(postMessage, DataModel.PostMessage::class.java) //param 을동적으로 파싱..
        //성공실패여부
        success = parseMessage.success?:""
        fail = parseMessage.fail?:""

        when(WebViewFunctionType.valueOf(parseMessage.name)){
            WebViewFunctionType.setTopMenu -> { //상단 탭관련 내용
                setTopMenu(parseMessage)
            }
            WebViewFunctionType.addHistory ->{ //화면 액션에 따른 히스토리를 관리함
                addHistory(parseMessage)
            }
            WebViewFunctionType.navigation ->{ //화면별 이동을 구분해서 동작
                moveViewNavigation(parseMessage)
            }//navigation end
            WebViewFunctionType.closePopup ->{ //스크립트로 오픈된 팝업형태의 창을 닫음
                closePopup(parseMessage)
            }
            WebViewFunctionType.deviceInfo -> { //디바이스 기기 정보 관련 전달
                deviceInfoSend(parseMessage)
            }
            WebViewFunctionType.sendSms -> { //문자메세지 SEND 관련
                sendSMS(parseMessage)
            }
            WebViewFunctionType.getSnsUserInfo -> { //유저관련 회원가입, 로그인...
                getSnsUserInfo(parseMessage)
            }
            WebViewFunctionType.toastMsg -> { //토스트 메세지 출력
                showToastMsg(parseMessage)
            }
            WebViewFunctionType.downloadImage -> { //이미지를 다운로드 한다
                downloadImg(parseMessage)
            }
            WebViewFunctionType.webShare -> { //웹에서 공유바를 show 한다.
                showWebSharedBar(parseMessage)
            }
            WebViewFunctionType.setLoginInfo -> { //유저정보를 저장 한다.
                setLoginInfo(parseMessage)
            }
            WebViewFunctionType.getLocation -> { //위치정보를 전달한다.
                getCurrentLocation(parseMessage)
            }
            WebViewFunctionType.openUrl -> { //위치정보를 전달한다.
                moveOpenUrl(parseMessage)
            }



            //... 계속 추가중







        }//when end

    }

}//postMessage end

setTopMenu

  • 상단바의 제목과 back 버튼을 정의한다.

fun setTopMenu(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.SetTopMenuParams::class.java)
    //해당작업진행
    with(parseParams){
        Log.i(TAG, preBt) //Y/N android back은 물리버튼이 있기때문에 의미가 있나...
        Log.i(TAG, title) //text_top_title?.text = title
    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

addHistory

  • history를 추가한다.

fun addHistory(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.AddHistoryParams::class.java)
    //해당작업진행
    with(parseParams){
        Log.i(TAG, url)
        Log.i(TAG, param.toString())
        Log.i(TAG, type)

        if(mContext is CommonWebViewConroller){
            mContext.historyList.add(WebHistory(url, param.toString(), type))
        }

    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

moveViewNavigation

  • 요청값에 따라 화면을 이동시킨다.

fun moveViewNavigation(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.NavigationParams::class.java)
    //해당작업진행

    val naviType = parseParams.type.toUpperCase()
    when(naviType){
        "C" -> { //현재 보여지는 화면을 닫는다.
            mContext?.finish()
        }
        "N" -> { //기존화면 그대로, new page 새로운 액티비티에서 웹뷰를 보여준다.
            val intent: Intent = Intent(mContext, MainWebViewActivity::class.java)
            intent.putExtra("url", parseParams.url)
            intent.putExtra("param", parseParams.param?.toString())
            intent.putExtra("viewType", naviType)
            mContext?.startActivityForResult(intent, REQUEST_NEW_WEBVIEW_OPEN)
        }
        "N-R", "N-RT", "N-RB", "N-RTB" -> { //현재 화면이 finish 되고 새로운 액티비티에서 웹뷰를 보여준다.
            val intent: Intent = Intent(mContext, MainWebViewActivity::class.java)
            intent.putExtra("url", parseParams.url)
            intent.putExtra("param", parseParams.param?.toString())
            intent.putExtra("viewType", naviType)
            intent.putExtra("rootView", true)
            mContext?.startActivityForResult(intent, REQUEST_NEW_WEBVIEW_OPEN)
            mContext?.finish()
        }
        "B" -> { //현재 보여지는 화면의 history back(히스토리없으면 finish)
            (mContext as? CommonWebViewConroller)?.backAction()
        }
        "M" -> { // 메인으로 이동
            this@CustomWebView.loadUrl(MAIN_PAGE_URL)
        }
        "P" ->{ //기존화면 그대로, 새로화면 열고 로직처리되면 닫히면서, 기존창에서 스크립트를 호출
            val intent: Intent = Intent(mContext, MainWebViewActivity::class.java)
            intent.putExtra("url", parseParams.url)
            intent.putExtra("param", parseParams.param?.toString())
            intent.putExtra("viewType", naviType)
            mContext?.startActivityForResult(intent, REQUEST_NEW_WEBVIEW_CALLBACK_OPEN)
        }
        "SETTING" ->{ //권한 관련 등의 이유로 설정화면으로 이동할때
            //TODO 무슨권한 페이지로?
            val builder = AlertDialog.Builder(context)
            builder.setTitle("확인")
            builder.setMessage("설정페이지로 이동하시겠습니까?")
            builder.setPositiveButton(android.R.string.yes) { d, _ ->
                d.dismiss()

                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                    Uri.parse("package: ${mContext?.packageName}"))
                intent.addCategory(Intent.CATEGORY_DEFAULT)
                mContext?.startActivityForResult(intent, REQUEST_SETTING_OPEN)

            }
            builder.setNegativeButton(android.R.string.no){d, _->
                d.dismiss()
            }
            builder.show()

        }
        "EXIT" -> {
            mContext?.finishApp("종료하시겠습니까?")
        }else -> { //예외적으로 처리할 navi 동작을 정의

        }

    }//when end

    responseSuccessOrFail(COMMON_SUCCESS, null)
}

closePopUp

  • 종료 팝업을 호출한다.

fun closePopup(parseMessage: DataModel.PostMessage){
    val parseParams = Gson().fromJson(parseMessage.params, DataModel.ClosePopupParams::class.java)
    //해당작업진행
    with(parseParams){

        val func:String = function
        val param:String = param?.toString()?: ""

        val intent = Intent()
        intent.putExtra("function",func)
        intent.putExtra("param",param)
        mContext?.setResult(Activity.RESULT_OK, intent)
        mContext?.finish()

    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

deviceInfoSend

  • 기기 정보를 넘겨준다.

  • 필수 권한 정보승인여부를 넘겨준다.

fun deviceInfoSend(parseMessage: DataModel.PostMessage?){
    val responseJson = JsonObject()
    responseJson.addProperty("appVer", context.getAppVersionName())
    responseJson.addProperty("build", context.getAppVersionCode())
    responseJson.addProperty("os",context.getAppOs(0))
    responseJson.addProperty("osVer",context.getOsVersion())
    responseJson.addProperty("model",context.getDeviceModel())
    responseJson.addProperty("uuid",context.getDeviceUuid())
    responseJson.addProperty("pushToken",context.getPushToken())
    responseJson.addProperty("isFirst",SharedPreferenceManager.isFirst())

    val widthHeight = context.getWidthAndHeight("px",mContext!!)
    responseJson.addProperty("width",widthHeight[0]) //width
    responseJson.addProperty("height",widthHeight[1]) //height

    responseJson.addProperty("carrierName",context.getTelecomName()) //통신사
    responseJson.addProperty("carrierCode",context.getTelecomCode()) //통신사

    //권한 (아이폰 안드로이드 권한 다름 체크 )
    //TODO 권한 전달 작업
    val userPermMap = context.getUserPermission()
    responseJson.addProperty("p-mike", userPermMap["mic"]) //마이크권한
    responseJson.addProperty("p-photo", userPermMap["file"]) //사진권한 (file)
    responseJson.addProperty("p-camera", userPermMap["camera"]) //카메라권한
    responseJson.addProperty("p-contact", userPermMap["contact"]) //연락처권한
    responseJson.addProperty("p-location", userPermMap["location"]) //위치권한
    responseJson.addProperty("p-phone", userPermMap["phone"]) //폰권한
    responseJson.addProperty("p-push", userPermMap["push"]) //푸시권한

    responseSuccessOrFail(COMMON_SUCCESS, responseJson)
}

sendSMS

  • 문자를 전송한다.

fun sendSMS(parseMessage: DataModel.PostMessage){

    val parseParams = Gson().fromJson(parseMessage.params, DataModel.sendSmsParams::class.java)
    //해당작업진행
    with(parseParams){
        //val numbers = number
        var resultNumbers = ""
        number.forEach {
            resultNumbers += "${it.toString().replace("\"", "")};"
        }

        val msg:String = message

        try{
            val smsUri = Uri.parse("smsto: $resultNumbers")
            val sendIntent = Intent(Intent.ACTION_SENDTO, smsUri)
            sendIntent.putExtra("sms_body", msg)
            mContext?.startActivity(sendIntent)

        }catch (e:Exception){
            Log.e(TAG, e.toString())
        }
    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

getSnsUserInfo

  • sns로 로그인 한 사용자의 채널을 가져온다.

fun getSnsUserInfo(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.snsUserInfoParams::class.java)

    if (parseParams.type == "0") { //0 카톡 , 1 페북
        Log.i(TAG, "0 카톡")
        commonWebviewCont?.kakaoLogin?.customKakaoLogin(mContext)
    }else{
        Log.i(TAG, "1 페북")
        commonWebviewCont?.facebookLogin?.callFaceBook()
    }

}

showToastMsg

  • JS에서 전달받은 토스트 메시지를 출력한다.

fun showToastMsg(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, JsonObject::class.java)
        Toast.makeText(mContext, parseParams["msg"].toString(), Toast.LENGTH_SHORT).show()
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

downloadImg

  • 이미지를 다운로드한다.

fun downloadImg(parseMessage: DataModel.PostMessage) {
    (mContext as? CommonWebViewConroller)?.showLoadingBar()
    var counting = 0

    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.downloadImgParams::class.java)

    parseParams.info.forEach { info ->
        val downFileinfo =
            Gson().fromJson(info, DataModel.downloadImgInfo::class.java)

        ApiManager.downloadImg(downFileinfo.link)
            .flatMap { img -> saveFileProcess(img, downFileinfo.name) }
            .subscribeOn(Schedulers.single()) //순차 진행...
            .observeOn(AndroidSchedulers.mainThread())

            .doOnComplete { //complete 시 dispose 자동
                Log.i(TAG, "complete downloadImgApiService")
                counting += 1
                Log.i(TAG, "=== ${counting * 100 / parseParams.info.size()} % ===")
                if(counting == parseParams.info.size()){ //last index
                    (mContext as? CommonWebViewConroller)?.hideLoadingBar()
                    val complete = context.getString(R.string.toast_msg_file_down_ok)
                    Toast.makeText(mContext, complete, Toast.LENGTH_SHORT).show()
                }
            }
            .doOnError { //다운로드중 error 발생
                    error ->
                Log.e(TAG, error.message)
                (mContext as? CommonWebViewConroller)?.hideLoadingBar()
                val failed = context.getString(R.string.toast_msg_file_down_fail)
                Toast.makeText(mContext, failed, Toast.LENGTH_SHORT).show()
            }
            .doOnNext {file->
                Log.i(TAG, file.absolutePath)
            }
            .subscribe()

    }
}

showWebSharedBar

  • 공유하기 동작에 대한 것을 정의한다.

fun showWebSharedBar(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, JsonObject::class.java)

    val intent = Intent(Intent.ACTION_SEND)
    intent.type = "text/plain"
    val shareBodyText = parseParams["text"]?.toString()
    intent.putExtra(Intent.EXTRA_SUBJECT, "Subject/Title")
    intent.putExtra(Intent.EXTRA_TEXT, shareBodyText)
    mContext?.startActivity(Intent.createChooser(intent, "공유 선택"))

    responseSuccessOrFail(COMMON_SUCCESS, null)
}

responseSuccessOrFail

  • Success / Fail JS를 호출한다.

@JavascriptInterface
fun responseSuccessOrFail(type:Int, params:JsonObject?){
    val resultCallJs = if(type == COMMON_SUCCESS){
        if(success != "") {
            "javascript:$success( ${params?.toString()?:""} )"
        }else{
            return
        }

    }else{ //fail
        if(fail != "") {
            "javascript:$fail( ${params?.toString()?:""} )"
        }else{
            return
        }
    }

    this@CustomWebView.post {
        loadUrl(resultCallJs)
    }

}

setLoginInfo

  • 로그인 이후 서버에서 가져온 사용자 정보를 저장한다.

fun setLoginInfo(parseMessage: DataModel.PostMessage){
    val parseParams:DataModel.loginUserInfoParams? =
        Gson().fromJson(parseMessage.params, DataModel.loginUserInfoParams::class.java)

    if(parseParams != null){
        //user 정보 저장
        SharedPreferenceManager.setIuid(parseParams.iuid?:"")
    }

}

getCurrentLocation

  • 현재 위치정보를 가져와 서버에 전달한다.

fun getCurrentLocation(parseMessage: DataModel.PostMessage){
    if(mContext?.getLocationCheckPermission()!!){// 권한 체크
        getAndSendLocation()
    }else{
        //위치 권한만 요청
        mContext.requestPermissionLocation(mContext)
    }

}

getAndSendLocation

  • 위치정보를 가져온다.

@SuppressLint("MissingPermission")
fun getAndSendLocation(){
    val mLocationManager = mContext?.getSystemService(AppCompatActivity.LOCATION_SERVICE) as LocationManager

    // GPS / NETWORK 사용 유무 먼저 확인후 진행
    // 둘다 켜져있으면 GPS 우선
    if(mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
            0, 10F, mGetLocationAndResultSendListener)
        return
    }else{
        if(mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)){
            mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                0, 10F, mGetLocationAndResultSendListener)
        }else{
            responseSuccessOrFail(COMMON_FAIL, null)
            Toast.makeText(mContext, "위치서비스 및 네트워크를 활성화 해주십시요.", Toast.LENGTH_SHORT).show()
        }
        return
    }

}

moveOpenUrl

  • 아웃링크로 이동하는 동작을 정의한다.

fun moveOpenUrl(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.openUrlParams::class.java)
    val openUrl = parseParams.param

    try{
        val ii = Intent(Intent.ACTION_VIEW)
        ii.data = Uri.parse(openUrl)
        mContext?.startActivity(ii)
    }catch (e: ActivityNotFoundException){ //해당 url로 호출 못 할 경우발생
        //TODO 스킴등을 호출로 못찾은 경우 실패 전달
        responseSuccessOrFail(COMMON_FAIL, null)
    }
}

WebClient

  • url 이동 동작을 정의한다.

  • html링크 클릭, url 인터셉터,폼 등록 등의 동작들을 정의할 때 사

private inner class WebClient : WebViewClient() {

    /*
    override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
        Log.i(TAG, "request headers: ${request?.requestHeaders.toString()}")
        return super.shouldInterceptRequest(view, request)
    }*/

    //TODO 작성 완료 되면 24 이하 shouldOverrideUrlLoading 에도 작성
    @TargetApi(Build.VERSION_CODES.N)
    override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {

        //카카오톡 링크 관련
        if(request.url.toString().contains("intent:kakaolink://send")){
            try {
                val urlExternalBrowser = request.url.toString()
                val intent = Intent.parseUri(urlExternalBrowser, Intent.URI_INTENT_SCHEME)
                //val existPack = mContext?.packageManager?.getLaunchIntentForPackage(intent.`package`!!)
                //if(existPack != null){
                    mContext?.startActivity(intent)
                //}

            }catch(e:ActivityNotFoundException){ //앱 설치 안된경우 처리
                val url:Uri = Uri.parse("https://play.google.com/store/apps/details?id=com.kakao.talk&hl=ko")
                val intent = Intent(Intent.ACTION_VIEW)
                intent.setData(url)
                if(intent.resolveActivity(mContext?.packageManager!!) != null){
                    context.startActivity(intent)
                }

            }
        }else{
            val url = request.url.toString()
            this@CustomWebView.loadUrl(url)
            (mContext as? MainWebViewActivity)?.changeSelectBottomMenu(url)
        }
        return true
    }

    override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
        return false
    }

    override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
        super.onPageStarted(view, url, favicon)
        (mContext as? CommonWebViewConroller)?.showLoadingBar()
    }

    override fun onPageFinished(view: WebView, url: String) {
        super.onPageFinished(view, url)
        //webView.clearHistory();
        this@CustomWebView.clearCache(true)

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            //noinspection deprecation
            CookieSyncManager.getInstance().sync()
        } else {
            //쿠키 수동 싱크 (메모리 -> 로컬저장) 그밖에 싱크는 webview 에서 자동으로 함.
            CookieManager.getInstance().flush()
        }

        //reload 콜 활성화(맨처음 웹 로드 된후)
        if((mContext as? CommonWebViewConroller)?.isEnabledReload == false){
            (mContext as? CommonWebViewConroller)?.isEnabledReload = true
        }
        (mContext as? CommonWebViewConroller)?.hideLoadingBar()
    }

}

ChroniumClient

  • 경고표시나 윈도우 닫기 등의 Web 브라우저 이벤트를 구현하기 위한 클래스

  • 새창 띄우기 동작에 관한 정의

  • 이미지 선택에 관한 동작 정의

 public inner class ChroniumClient : WebChromeClient() {

        override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean {
            return super.onJsAlert(view, url, message, result)
        }

        override fun onReceivedTitle(view: WebView, title: String?) {
            super.onReceivedTitle(view, title)
            if (title != null) {
                //text_top_title?.text = title
            }
        }

        /**
         * window.open (팝업) 호출시의 동작 정의
         *
         * */
        override fun onCreateWindow(view: WebView?,isDialog: Boolean,
            isUserGesture: Boolean,resultMsg: Message?): Boolean {

            //중요사항!!! : 새로운 웹뷰를 타겟팅 하지 않으면 IllegalArgumentException 가 발생 할 수 있음.
            //"New WebView for popup window must not have been previously navigated."
            val newWebView = NewWindowWebView(context).apply {
                this.newWindowDepth = 0
            }
            (mContext as? MainWebViewActivity)?.createNewWindowWebView(0, newWebView)
            val transport:WebViewTransport? = resultMsg?.obj as? WebViewTransport
            transport?.setWebView(newWebView)
            resultMsg?.sendToTarget()

            return true
        }

        protected fun openFileChooser(uploadMsg: ValueCallback<Uri?>) {
            mUploadMessage = uploadMsg
            val i = Intent(Intent.ACTION_GET_CONTENT)
            i.addCategory(Intent.CATEGORY_OPENABLE)
            i.type = "image/*"

            mContext?.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_CHOOSER_REQUEST_CODE)
        }

        // For 3.0+ Devices (Start)
        // onActivityResult attached before constructor
        protected fun openFileChooser(uploadMsg: ValueCallback<Uri?>, acceptType: String) {
            mUploadMessage = uploadMsg
            val i = Intent(Intent.ACTION_GET_CONTENT)
            i.addCategory(Intent.CATEGORY_OPENABLE)
            i.type = "image/*"

            mContext?.startActivityForResult(Intent.createChooser(i, "File Browser"), FILE_CHOOSER_REQUEST_CODE)
        }

        //For Android 4.1+
        fun openFileChooser(uploadMsg: ValueCallback<Uri?>, acceptType: String, capture: String) {

            if (mUploadMessage != null) {
                mUploadMessage!!.onReceiveValue(null)
            }
            mUploadMessage = uploadMsg

            //카메라intent
            var takePictureIntent: Intent? = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            if (takePictureIntent!!.resolveActivity(mContext?.packageManager) != null) {
                var photoFile: File? = null
                try {
                    photoFile = createImageFile()
                    takePictureIntent.putExtra("PhotoPath", mCM)
                } catch (ex: IOException) {
                    Log.e(TAG, "Image file creation failed", ex)
                }

                if (photoFile != null) {
                    mCM = "file:" + photoFile.absolutePath
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile))
                } else {
                    takePictureIntent = null
                }
            }

            //image pick
            val contentSelectionIntent = Intent()//Intent.ACTION_GET_CONTENT
            contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
            contentSelectionIntent.setAction(Intent.ACTION_GET_CONTENT)
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
            contentSelectionIntent.type = "image/*"

            val intentArray: Array<Intent>
            if (takePictureIntent != null) {
                intentArray = arrayOf(takePictureIntent)
            } else {
                intentArray = arrayOf(Intent())
            }

            val chooserIntent = Intent(Intent.ACTION_CHOOSER)
            chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser")
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)

            mContext?.startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE)

        }

        //For Android 5.0+
        override fun onShowFileChooser( webView: WebView,filePathCallback: ValueCallback<Array<Uri?>>,
                                        fileChooserParams: WebChromeClient.FileChooserParams): Boolean {

            if (mUploadMessage2 != null) {
                mUploadMessage2!!.onReceiveValue(null)
            }
            mUploadMessage2 = filePathCallback

            //카메라intent
            var takePictureIntent: Intent? = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            if (takePictureIntent!!.resolveActivity(mContext?.packageManager) != null) {
                var photoFile: File? = null
                try {
                    photoFile = createImageFile()
                    takePictureIntent.putExtra("PhotoPath", mCM)
                } catch (ex: IOException) {
                    Log.e(TAG, "Image file creation failed", ex)
                }

                if (photoFile != null) {
                    mCM = "file:" + photoFile.absolutePath
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile))
                } else {
                    takePictureIntent = null
                }
            }
            //image pick
            val contentSelectionIntent = Intent()//Intent.ACTION_GET_CONTENT
            contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
            contentSelectionIntent.setAction(Intent.ACTION_GET_CONTENT)
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
            contentSelectionIntent.type = "image/*"

            val intentArray: Array<Intent>
            if (takePictureIntent != null) {
                intentArray = arrayOf(takePictureIntent)
            } else {
                intentArray = arrayOf(Intent())
            }

            val chooserIntent = Intent(Intent.ACTION_CHOOSER)
            chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser")
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)

            mContext?.startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE)
            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, /* prefix */
                ".jpg", /* suffix */
                storageDir /* directory */
            )
        }

    }


}//class end

Last updated

Was this helpful?