Kotlin сопрограммы: создать поток, выполняющий код, пока другой ожидает завершения первого - PullRequest
1 голос
/ 01 апреля 2020

Я нашел следующий код из https://github.com/blink22/react-native-html-to-pdf/blob/master/android/src/main/java/android/print/PdfConverter.java, преобразованный в Kotlin:

import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.ParcelFileDescriptor
import android.print.PrintAttributes.Resolution
import android.print.PrintDocumentAdapter.LayoutResultCallback
import android.print.PrintDocumentAdapter.WriteResultCallback
import android.util.Log
import android.webkit.WebView
import android.webkit.WebViewClient
import java.io.File


/**
 * Converts HTML to PDF.
 *
 *
 * Can convert only one task at a time, any requests to do more conversions before
 * ending the current task are ignored.
 */
class PdfConverter private constructor() : Runnable {
    private var mContext: Context? = null
    private var mHtmlString: String? = null
    private var mPdfFile: File? = null
    private var mPdfPrintAttrs: PrintAttributes? = null
    private var mIsCurrentlyConverting = false
    private var mWebView: WebView? = null
    private var done: Boolean = false

    override fun run() {
        mWebView = WebView(mContext as Context)
        mWebView!!.webViewClient = object : WebViewClient() {
            override fun onPageFinished(view: WebView, url: String) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) throw RuntimeException(
                    "call requires API level 19"
                ) else {
                    val documentAdapter =
                        mWebView!!.createPrintDocumentAdapter()
                    documentAdapter.onLayout(
                        null,
                        pdfPrintAttrs,
                        null,
                        object : LayoutResultCallback() {},
                        null
                    )
                    documentAdapter.onWrite(
                        arrayOf(PageRange.ALL_PAGES),
                        outputFileDescriptor,
                        null,
                        object : WriteResultCallback() {
                            override fun onWriteFinished(pages: Array<PageRange>) {
                                destroy()
                                done = true
                            }
                        })

                }
                Log.d("end of onpagefinished()", "end of onpagefinished()")
            }
        }
        mWebView!!.loadData(mHtmlString, "text/HTML", "UTF-8")
        Log.d("end of run()", "end of run()")

    }


    var pdfPrintAttrs: PrintAttributes?
        get() = if (mPdfPrintAttrs != null) mPdfPrintAttrs else defaultPrintAttrs
        set(printAttrs) {
            mPdfPrintAttrs = printAttrs
        }

    fun convert(
        context: Context?,
        htmlString: String?,
        file: File?
    ) {
        requireNotNull(context) { "context can't be null" }
        requireNotNull(htmlString) { "htmlString can't be null" }
        requireNotNull(file) { "file can't be null" }
        if (mIsCurrentlyConverting) return
        mContext = context
        mHtmlString = htmlString
        mPdfFile = file
        mIsCurrentlyConverting = true
        runOnUiThread(this)
        Log.d("end of convert()","end of convert()")
    }

    private val outputFileDescriptor: ParcelFileDescriptor?
        private get() {
            try {
                mPdfFile!!.createNewFile()
                Log.d("outputfiledescriptor","the file has been created")
                return ParcelFileDescriptor.open(
                    mPdfFile,
                    ParcelFileDescriptor.MODE_TRUNCATE or ParcelFileDescriptor.MODE_READ_WRITE
                )
            } catch (e: Exception) {
                Log.d(TAG, "Failed to open ParcelFileDescriptor", e)
            }
            return null
        }

    private val defaultPrintAttrs: PrintAttributes?
        private get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) null else PrintAttributes.Builder()
            .setMediaSize(PrintAttributes.MediaSize.NA_GOVT_LETTER)
            .setResolution(Resolution("RESOLUTION_ID", "RESOLUTION_ID", 600, 600))
            .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
            .build()

    private fun runOnUiThread(runnable: Runnable) {
        val handler = Handler(mContext!!.mainLooper)
        handler.post(this)
    }

    private fun destroy() {
        mContext = null
        mHtmlString = null
        mPdfFile = null
        mPdfPrintAttrs = null
        mIsCurrentlyConverting = false
        mWebView = null
        Log.d("end of destroy()","end of destroy()")
    }

    companion object {
        private const val TAG = "PdfConverter"
        private var sInstance: PdfConverter? = null
        @get:Synchronized
        val instance: PdfConverter?
            get() {
                if (sInstance == null) sInstance =
                    PdfConverter()
                return sInstance
            }
    }
}

Я хочу, чтобы выполнение ожидало onWriteFinished до go назад до runOnUiThread. Также я хочу, чтобы основной поток выполнил run. Поэтому я попытался сделать это с помощью следующего кода, используя сопрограммы:

package android.print

import kotlinx.coroutines.runBlocking

import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.ParcelFileDescriptor
import android.print.PrintAttributes.Resolution
import android.print.PrintDocumentAdapter.LayoutResultCallback
import android.print.PrintDocumentAdapter.WriteResultCallback
import android.util.Log
import android.webkit.WebView
import android.webkit.WebViewClient
import java.io.File


/**
 * Converts HTML to PDF.
 *
 *
 * Can convert only one task at a time, any requests to do more conversions before
 * ending the current task are ignored.
 */
class PdfConverter2 {
    private var mContext: Context? = null
    private var mHtmlString: String? = null
    private var mPdfFile: File? = null
    private var mPdfPrintAttrs: PrintAttributes? = null
    private var mIsCurrentlyConverting = false
    private var mWebView: WebView? = null
    private var done: Boolean = false

    suspend fun run() {
        Log.d("run()","is this the main thread :"+(Looper.myLooper() == Looper.getMainLooper()))
        mWebView = WebView(mContext as Context)
        mWebView!!.webViewClient = object : WebViewClient() {
            override fun onPageFinished(view: WebView, url: String) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) throw RuntimeException(
                    "call requires API level 19"
                ) else {
                    val documentAdapter =
                        mWebView!!.createPrintDocumentAdapter()
                    documentAdapter.onLayout(
                        null,
                        pdfPrintAttrs,
                        null,
                        object : LayoutResultCallback() {},
                        null
                    )
                    documentAdapter.onWrite(
                        arrayOf(PageRange.ALL_PAGES),
                        outputFileDescriptor,
                        null,
                        object : WriteResultCallback() {
                            override fun onWriteFinished(pages: Array<PageRange>) {
                                destroy()
                                done = true
                            }
                        })

                }
                Log.d("end of onpagefinished()", "end of onpagefinished()")
            }
        }
        mWebView!!.loadData(mHtmlString, "text/HTML", "UTF-8")
        Log.d("end of run()", "end of run()")

    }


    var pdfPrintAttrs: PrintAttributes?
        get() = if (mPdfPrintAttrs != null) mPdfPrintAttrs else defaultPrintAttrs
        set(printAttrs) {
            mPdfPrintAttrs = printAttrs
        }

    fun convert(
        context: Context?,
        htmlString: String?,
        file: File?
    ) {
        requireNotNull(context) { "context can't be null" }
        requireNotNull(htmlString) { "htmlString can't be null" }
        requireNotNull(file) { "file can't be null" }
        if (mIsCurrentlyConverting) return
        mContext = context
        mHtmlString = htmlString
        mPdfFile = file
        mIsCurrentlyConverting = true
        runOnUiThread()
        Log.d("end of convert()","end of convert()")
    }

    private val outputFileDescriptor: ParcelFileDescriptor?
        private get() {
            try {
                mPdfFile!!.createNewFile()
                Log.d("outputfiledescriptor","the file has been created")
                return ParcelFileDescriptor.open(
                    mPdfFile,
                    ParcelFileDescriptor.MODE_TRUNCATE or ParcelFileDescriptor.MODE_READ_WRITE
                )
            } catch (e: Exception) {
                Log.d(TAG, "Failed to open ParcelFileDescriptor", e)
            }
            return null
        }

    private val defaultPrintAttrs: PrintAttributes?
        private get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) null else PrintAttributes.Builder()
            .setMediaSize(PrintAttributes.MediaSize.NA_GOVT_LETTER)
            .setResolution(Resolution("RESOLUTION_ID", "RESOLUTION_ID", 600, 600))
            .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
            .build()

    private fun runOnUiThread() {
        runBlocking {
            run()
            while(!done){

            }
        }

    }

    private fun destroy() {
        mContext = null
        mHtmlString = null
        mPdfFile = null
        mPdfPrintAttrs = null
        mIsCurrentlyConverting = false
        mWebView = null
        Log.d("end of destroy()","end of destroy()")
    }

    companion object {
        private const val TAG = "PdfConverter2"
        private var sInstance: PdfConverter2? = null
        @get:Synchronized
        val instance: PdfConverter2?
            get() {
                if (sInstance == null) sInstance =
                    PdfConverter2()
                return sInstance
            }
    }
}

Также в другом файле есть функция, которая вызывает PdfConverter и вызывает PdfConverter.

fun createPdfFromHtml(htmlstring: String) {
       val converter: PdfConverter? = PdfConverter.instance
        val file = File(
            Environment.getExternalStorageDirectory().getPath().toString() + "/" + name_of_directory_of_pdfs + "/",
            nameofpdf
        )
        converter?.convert(m_context, htmlstring, file)
        mFilepdf = file
}

То, что я хочу, - это выполнение кода останавливается в «конвертере? Я подумал, что другой senario останавливается на runonUiThread и ждет повторного запуска onWriteFinished.

После ответа @ m0skit0 я изменяю последний код:

fun createPdfFromHtml(htmlstring: String) {
        val file = File(
            Environment.getExternalStorageDirectory().path.toString() + "/" + name_of_directory_of_pdfs + "/",
            nameofpdf
        )
        var converter = PdfConverter3.from(m_context)
        GlobalScope.launch(Dispatchers.IO) {// I TRY ALSO Dispatchers.Main
           converter.convert(htmlstring, file)
        }
        mFilepdf = file
        Log.d("mich/createPDfFromHtml", "at the end of createPdfFromHtml, is this the main thread ? "+ (Looper.myLooper() == Looper.getMainLooper()))
}

Но вещь снова существует.

Ответы [ 2 ]

1 голос
/ 01 апреля 2020

Вот мой взгляд на перевод этого класса в Kotlin с использованием сопрограмм

package org.m0skit0.android.testapp

import android.annotation.TargetApi
import android.content.Context
import android.os.ParcelFileDescriptor
import android.print.PageRange
import android.print.PrintAttributes
import android.print.PrintDocumentAdapter
import android.webkit.WebView
import android.webkit.WebViewClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

@TargetApi(19)
class PdfConverter private constructor(private val context: Context) {

    private val defaultPrintAttributes: PrintAttributes by lazy {
        PrintAttributes.Builder()
            .setMediaSize(PrintAttributes.MediaSize.NA_GOVT_LETTER)
            .setResolution(PrintAttributes.Resolution("RESOLUTION_ID", "RESOLUTION_ID", 600, 600))
            .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
            .build()
    }

    private var printAttributes: PrintAttributes? = null

    fun printAttributes(printAttributes: PrintAttributes): PdfConverter = apply {
        this.printAttributes = printAttributes
    }

    suspend fun convert(htmlString: String, pdfFile: File) {
        withContext(Dispatchers.Main) {
            suspendCoroutine<Unit> { continuation ->
                WebView(context).apply {
                    webViewClient = WebViewClientImpl(pdfFile, continuation)
                }.loadData(htmlString, "text/html", "UTF-8")
            }
        }
    }

    private fun File.outputFileDescriptor(): ParcelFileDescriptor? =
        try {
            createNewFile()
            ParcelFileDescriptor.open(this, ParcelFileDescriptor.MODE_TRUNCATE or ParcelFileDescriptor.MODE_READ_WRITE)
        } catch (e: Exception) {
            null
        }

    companion object {
        fun from(context: Context): PdfConverter = PdfConverter(context)
    }

    private inner class WebViewClientImpl(private val file: File, private val continuation: Continuation<Unit>) : WebViewClient() {
        override fun onPageFinished(webView: WebView, url: String) {
            webView.createPrintDocumentAdapter()?.run {
                onLayout(
                    null,
                    printAttributes ?: defaultPrintAttributes,
                    null,
                    object : PrintDocumentAdapter.LayoutResultCallback() {},
                    null
                )
                onWrite(
                    arrayOf(PageRange.ALL_PAGES),
                    file.outputFileDescriptor(),
                    null,
                    object : PrintDocumentAdapter.WriteResultCallback() {
                        override fun onWriteCancelled() {
                            super.onWriteCancelled()
                            continuation.resume(Unit)
                        }

                        override fun onWriteFailed(error: CharSequence?) {
                            super.onWriteFailed(error)
                            continuation.resumeWithException(Exception(error.toString()))
                        }

                        override fun onWriteFinished(pages: Array<out PageRange>?) {
                            super.onWriteFinished(pages)
                            continuation.resume(Unit)
                        }
                    }
                )
            }
        }
    }
}
0 голосов
/ 01 апреля 2020

если я вас правильно понял, вы хотите, чтобы запуск выполнялся в главном потоке. Чтобы добиться этого, вместо запуска функции suspend вы могли бы предоставить функции launch сопрограмму с областью действия Dispatchers.Main.

...