亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ComposeDesktop開發桌面端多功能APK工具怎么使用

發布時間:2022-08-10 17:35:17 來源:億速云 閱讀:295 作者:iii 欄目:開發技術

這篇文章主要講解了“ComposeDesktop開發桌面端多功能APK工具怎么使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“ComposeDesktop開發桌面端多功能APK工具怎么使用”吧!

    功能一覽

    接下來先給大家一覽下桌面端工具的基本功能,我的電腦是Windows的,所以都是基于Windows平臺下的build-tools相關工具進行開發的。首先大部分的功能都是基于jar或exe文件,那么在Java(Kotlin)中我們可以通過如下方式來調用這些外部程序,exec其實最終也是調用了ProcessBuilder,整體的原理就是如此:

    //方式1
    Runtime.getRuntime().exec(cmd)
    //方式2
    ProcessBuilder(cmd)

    多渠道打包

    這是該工具最基本的功能,使用VasDolly方案對APK文件進行多渠道打包(當然該APK文件需要是簽名好的)。

    ComposeDesktop開發桌面端多功能APK工具怎么使用

    多渠道包命令行工具即 VasDolly.jar,該文件可以在上述GitHub倉庫中找到,常用的命令如下:

    // 獲取指定APK的簽名方式
    java -jar VasDolly.jar get -s [源apk地址]
    // 獲取指定APK的渠道信息
    java -jar VasDolly.jar get -c [源apk地址]
    // 刪除指定APK的渠道信息
    java -jar VasDolly.jar remove -c [源apk地址]
    // 通過指定渠道字符串添加渠道信息
    java -jar VasDolly.jar put -c "channel1,channel2" [源apk地址] [apk輸出目錄]
    // 通過指定某個渠道字符串添加渠道信息到目標APK
    java -jar VasDolly.jar put -c "channel1" [源apk地址] [輸出apk地址]
    // 通過指定渠道文件添加渠道信息
    java -jar VasDolly.jar put -c channel.txt [源apk地址] [apk輸出目錄]
    // 提供了FastMode,生成渠道包時不進行強校驗,速度可提升10倍以上
    java -jar VasDolly.jar put -c channel.txt -f [源apk地址] [apk輸出目錄]

    對齊和簽名

    上傳應用市場前,APK文件大部分會被市場要求進行加固,無論是使用騰訊樂固還是360加固等方式,加固后APK的簽名信息總會被破壞,所以我們需要對加固后的APK文件重新進行簽名。

    配置簽名

    首先我們需要準備好應用的簽名信息,該工具支持導入簽名文件,并保存相應的StorePass、KeyAlias、KeyPass信息,如下:

    ComposeDesktop開發桌面端多功能APK工具怎么使用

    當選擇APK后,程序會判斷選擇的APK是否進行了簽名,如果沒有簽名,那么就會彈窗提醒用戶選擇配置好的簽名文件進行簽名,簽名之后才可進行多渠道打包的過程。

    ComposeDesktop開發桌面端多功能APK工具怎么使用

    注:該功能現已升級,添加簽名文件的時候綁定包名,選擇apk后會自動獲取到包名然后查找到對應的簽名文件自動對齊簽名處理,無需手動進行選擇了。

    對齊

    簽名的過程則需要用到Android SDK中的兩個文件,以Windows系統為例,一個是處理對齊的【build-tools\版本號\zipalign.exe】文件,另一個則是用來簽名的【build-tools\版本號\lib\apksigner.jar】文件。

    我們先看下zipalign工具的官方說明:

    zipalign is a zip archive alignment tool. It ensures that all uncompressed files in the archive are aligned relative to the start of the file. This allows those files to be accessed directly via mmap(2), removing the need to copy this data in RAM and reducing your app's memory usage. zipalign是一種zip歸檔對齊工具。它確保存檔中所有未壓縮的文件都與文件的開頭對齊。這允許通過mmap直接訪問這些文件,無需將這些數據復制到RAM中,并減少應用程序的內存使用。

    zipalign should be used to optimize your APK file before distributing it to end-users. This is done automatically if you build with Android Studio. This documentation is for maintainers of custom build systems. 在將APK文件分發給用戶之前,應使用zipalign優化APK文件。如果您使用Android Studio進行構建,這將自動完成。本文檔面向定制構建系統的維護人員。

    Google官方現在要求在使用apksigner對APK文件進行簽名前需要先使用zipalign來優化APK文件,具體命令如下,以Windows下的zipalign.exe文件為例:

    //對齊APK
    zipalign.exe -p -f -v 4 [源apk路徑] [輸出apk路徑]
    //驗證APK是否對齊
    zipalign.exe -c -v 4 [源apk路徑]

    其他相關的內容可以參閱官網 zipalign 。

    簽名

    當APK文件對齊后,就可以給對齊后的APK進行簽名操作了,簽名的方法有兩種,我們這里單說使用--ks選項指定密鑰庫的方式,具體命令如下:

    java -jar apksigner.jar sign 
        --verbose 
        --ks [KeyStore文件路徑] 
        --ks-pass pass:[KeyStorePass]
        --ks-key-alias [KeyAlias]
        --key-pass pass:[KeyPass]
        --out [輸出apk路徑]
        [源apk路徑]

    命令本身很簡單,別搞錯參數就好,尤其是兩個密碼的參數,后面需要使用【pass:密碼】。輸入密碼這里還支持其他格式,如果有需要請參閱官網 apksigner 。

    加固、對齊、重簽名后,這個apk就可以進行多渠道打包的處理了,然后即可發布到相關市場和渠道。

    其他內容

    在項目中還有很多其他的相關配置,比如發版的時候需要對APP進行應用內的更新通知。那么就需要我們填寫發版的相關信息,版本名、版本號、更新日志等等內容都需要完善(可根據APK文件的命名來獲取部分信息),然后通過這些信息生成應用內部更新的SQL語句,發送釘釘通知給相關后臺人員處理。通知這一步又用到了釘釘的SDK,該工具支持配置釘釘機器人Webhook地址以及需要艾特的人員信息。

    打出來的這些包都需要統一上傳到云存儲上面,這一步使用了AWS的云存儲SDK,可以配置云存儲桶地址等信息,免去人工手動上傳apk的煩惱。上傳完畢后會根據文件名生成相應的下載鏈接并通知到釘釘群,以便市場人員獲取到渠道最新的推廣鏈接等。

    ComposeDesktop開發桌面端多功能APK工具怎么使用

    桌面端開發

    彈窗

    關于彈窗,ComposeDesktop同樣提供了Dialog可組合函數:

    @Composable 
    public fun Dialog(
        onCloseRequest: () -> kotlin.Unit, 
        state: androidx.compose.ui.window.DialogState, 
        visible: kotlin.Boolean, 
        title: kotlin.String, 
        icon: androidx.compose.ui.graphics.painter.Painter?, 
        undecorated: kotlin.Boolean, 
        transparent: kotlin.Boolean, 
        resizable: kotlin.Boolean, 
        enabled: kotlin.Boolean, 
        focusable: kotlin.Boolean, 
        onPreviewKeyEvent: (androidx.compose.ui.input.key.KeyEvent) -> kotlin.Boolean, 
        onKeyEvent: (androidx.compose.ui.input.key.KeyEvent) -> kotlin.Boolean, 
        content: @Composable() (DialogWindowScope.() -> kotlin.Unit)
        ): kotlin.Unit { /* compiled code */ }

    大部分的參數都可以直接看出他的作用,主要看一下state參數,該參數可以控制彈窗的位置及大小,例如我們配置一個在屏幕中央,寬高為500*300dp的彈窗,那么示例代碼如下:

    state = DialogState(
                position = WindowPosition(Alignment.Center),
                size = DpSize(500.dp, 300.dp),
            )

    不過這個彈窗沒有陰影,如果想添加的話可以內部套一層Surface來做出陰影效果:

    Surface(
        modifier = Modifier.fillMaxSize().padding(20.dp),
        elevation = 10.dp,
        shape = RoundedCornerShape(16.dp)
    )

    文件選擇器

    關于文件選擇器這一塊目前Compose還沒有專門的函數,但是我們還是可以使用原有的方案:

    javax.swing.JFileChooser

    java.awt.FileDialog

    個人還是更偏向于使用JFileChooser,因為使用第二種方案的話,在頁面重組的情況下總是會莫名的彈出選擇框來。一個簡單的文件選擇器如下所示:

    private fun showFileSelector(
        suffixList: Array<String>,
        onFileSelected: (String) -> Unit
    ) {
        JFileChooser().apply {
            //設置頁面風格
            try {
                val lookAndFeel = UIManager.getSystemLookAndFeelClassName()
                UIManager.setLookAndFeel(lookAndFeel)
                SwingUtilities.updateComponentTreeUI(this)
            } catch (e: Throwable) {
                e.printStackTrace()
            }
            fileSelectionMode = JFileChooser.FILES_ONLY
            isMultiSelectionEnabled = false
            fileFilter = FileNameExtensionFilter("文件過濾", *suffixList)
            val result = showOpenDialog(ComposeWindow())
            if (result == JFileChooser.APPROVE_OPTION) {
                val dir = this.currentDirectory
                val file = this.selectedFile
                println("Current apk dir: ${dir.absolutePath} ${dir.name}")
                println("Current apk name: ${file.absolutePath} ${file.name}")
                onFileSelected(file.absolutePath)
            }
        }
    }

    該方式在使用的過程中也有一定的缺陷,就是每次打開文件彈窗總是會卡頓一下,所以后續也是有了尋找其他高效選擇文件方式的想法。

    文件拖拽

    選擇文件除了上面的彈窗選擇方式,還有另一種神奇的方式 - 拖拽選擇,本來也是沒有頭緒,然而在Slack閑逛的時候發現了Jim Sproch推薦了一篇相關的文章:dev.to/tkuenneth/f&hellip; 。看完后也是恍然大悟,但是在Compose Desktop中,window是整個窗口,如何讓某一個指定的區域響應我們的文件拖拽事件呢?

    還記得在Android上有ComposeView吧,用來嵌套原來的那一套View體系。那么在這里我也是采用了類似的這么一種方式,實例一個空的JPanel控件然后給它安排到window中去。具體位置及大小的設置呢,在Compose中可以通過 onPlaced(onPlaced: (LayoutCoordinates) -> Unit) 修飾符來獲取到,示例代碼如下所示:

    @OptIn(ExperimentalComposeUiApi::class)
    @Composable
    fun DropBoxPanel(
        modifier: Modifier,
        window: ComposeWindow,
        component: JPanel = JPanel(),
        onFileDrop: (List<String>) -> Unit
    ) {
        val dropBoundsBean = remember {
            mutableStateOf(DropBoundsBean())
        }
        Box(
            modifier = modifier.onPlaced {
                dropBoundsBean.value = DropBoundsBean(
                    x = it.positionInWindow().x,
                    y = it.positionInWindow().y,
                    width = it.size.width,
                    height = it.size.height
                )
            }) {
            LaunchedEffect(true) {
                component.setBounds(
                    dropBoundsBean.value.x.roundToInt(),
                    dropBoundsBean.value.y.roundToInt(),
                    dropBoundsBean.value.width,
                    dropBoundsBean.value.height
                )
                window.contentPane.add(component)
                val target = object : DropTarget(component, object : DropTargetAdapter() {
                    override fun drop(event: DropTargetDropEvent) {
                        event.acceptDrop(DnDConstants.ACTION_REFERENCE)
                        val dataFlavors = event.transferable.transferDataFlavors
                        dataFlavors.forEach {
                            if (it == DataFlavor.javaFileListFlavor) {
                                val list = event.transferable.getTransferData(it) as List<*>
                                val pathList = mutableListOf<String>()
                                list.forEach { filePath ->
                                    pathList.add(filePath.toString())
                                }
                                onFileDrop(pathList)
                            }
                        }
                        event.dropComplete(true)
                    }
                }) {
                }
            }
            SideEffect {
                component.setBounds(
                    dropBoundsBean.value.x.roundToInt(),
                    dropBoundsBean.value.y.roundToInt(),
                    dropBoundsBean.value.width,
                    dropBoundsBean.value.height
                )
            }
            DisposableEffect(true) {
                onDispose {
                    window.contentPane.remove(component)
                }
            }
        }
    }

    實際運行效果如下,個人感覺基本還是能達到目的的:

    ComposeDesktop開發桌面端多功能APK工具怎么使用

    數據的保存

    最開始的時候,功能很少,每個配置的數據都是使用了txt文件來一行行保存,但是到了后來功能越來越復雜,單純的按行來處理貌似有點捉襟見肘了,所以考慮使用json來保存復雜的類型數據。

    json數據的處理從原生JSON到FastJson,Gson,Moshi等都已經體驗過了,于是乎便采用了之前未使用過的Jackson。然而不得不說,就目前為止,jackson是我用過最簡潔、優雅的一款解析庫。

    假如我有一個List類型的列表數據,那么當我要把這個數據存儲到文件的時候只需:

    jacksonObjectMapper().writeValue(File, List<String>)

    而從文件中讀取數據也是簡單的狠啊:

    //方式1
    val list = jacksonObjectMapper().readValue<List<String>>(jsonFile)
    //方式2
    val list : List<String> = jacksonObjectMapper().readValue(jsonFile)

    這種簡潔真的是深入我心。繼續深入了解下Jackson,你會發現它的可擴展性以及可定制性都很強,簡直相見恨晚啊。之前也是在一個舒適圈待習慣了,這次主動跳出來居然有了意想不到的收獲。

    但是呢,每個框架也會有它自己的注意點,比如jackson,屬性命名不可以是is開頭,否則序列化等就會報錯。這點似乎在阿里巴巴JAVA手冊中好像也有提到,具體原因請大家自行百度(Google)。

    資源的拷貝

    當我們使用[java -jar xxx.jar]命令執行jar文件的時候,需要明確指定 jar文件的地址,但是在Compose Desktop中我們要怎么存放并讀取這個jar文件呢 ?我們可以從Compose Desktop中讀取并展示圖片的相關代碼中得到啟發,假如有一個sample.svg圖標文件存放到了項目的 resources 文件夾下,那么我們在引用這張圖片的時候就可以使用:

    painterResource("sample.svg")

    我們點進去這個方法看下:

    @OptIn(ExperimentalComposeUiApi::class)
    @Composable
    fun painterResource(
        resourcePath: String
    ): Painter = painterResource(
        resourcePath,
        ResourceLoader.Default
    )
    @ExperimentalComposeUiApi
    @Composable
    fun painterResource(
        resourcePath: String,
        loader: ResourceLoader
    ): Painter = when (resourcePath.substringAfterLast(".")) {
        "svg" -> rememberSvgResource(resourcePath, loader)
        "xml" -> rememberVectorXmlResource(resourcePath, loader)
        else -> rememberBitmapResource(resourcePath, loader)
    }

    里面居然有個ResourceLoader類,這名字一聽就有戲啊,大概率就是我們需要的內容,而傳遞的默認參數是ResourceLoader.Default,那么就看下Default的源碼吧:

    //==========Resources.desktop.kt文件==========
    @ExperimentalComposeUiApi
    interface ResourceLoader {
        companion object {
            /**
             * Resource loader which is capable to load resources from `resources` folder in an application's
             * project. Ability to load from dependent modules resources is not guaranteed in the future.
             * Use explicit `ClassLoaderResourceLoader` instance if such guarantee is needed.
             */
            @ExperimentalComposeUiApi
            val Default = ClassLoaderResourceLoader()
        }
        fun load(resourcePath: String): InputStream
    }
    @ExperimentalComposeUiApi
    class ClassLoaderResourceLoader : ResourceLoader {
        override fun load(resourcePath: String): InputStream {
            // TODO(https://github.com/JetBrains/compose-jb/issues/618): probably we shouldn't use
            //  contextClassLoader here, as it is not defined in threads created by non-JVM
            val contextClassLoader = Thread.currentThread().contextClassLoader!!
            val resource = contextClassLoader.getResourceAsStream(resourcePath)
                ?: (::ClassLoaderResourceLoader.javaClass).getResourceAsStream(resourcePath)
            return requireNotNull(resource) { "Resource $resourcePath not found" }
        }
    }
    //==========ClassLoader類==========
    public InputStream getResourceAsStream(String name) {
        Objects.requireNonNull(name);
        URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (IOException e) {
            return null;
        }
    }
    public URL getResource(String name) {
        Objects.requireNonNull(name);
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = BootLoader.findResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

    上述源碼的整個邏輯基本上就是兩步,根據資源文件名獲取到資源文件,然后獲取資源文件的輸入流。看到這里其實我們已經有兩種方案了:

    • 方案一:直接拿到文件的URL然后獲取到文件的路徑

    • 方案二:根據文件的輸入流,將文件重新保存到本機相關目錄

    然而事情并沒有這么簡單,如果我們使用方案一,那么在編譯運行的時候完全沒有問題,所有的資源文件會被保存到【\build\processedResources\jvm】下,此時我們直接可以通過文件的URL獲取到文件路徑,然后調用即可。

    但是,當我們打包成安裝包后,例如在Windows下使用packageMsi命令打包出msi文件并安裝到電腦上后,運行程序,這時候你就會發現資源文件所在的路徑就很奇怪,例如我的工程下是【C:\Program Files\工程名\app\工程名-jvm-1.0-SNAPSHOT-xxxxxx.jar**!/**資源文件名】,也就是說所有的資源文件被打包進了這個快照文件,如果此時直接使用該路徑運行java -jar 等命令,那么肯定就會報錯了。

    所以最穩妥的方式還是使用方案二,使用ResourceLoader獲取到資源文件流然后重新保存到本機上的相關目錄就好了,偽代碼如下:

    ResourceLoader.Default.load(resourcesPath)
        .use { inputStream ->
            val fos = FileOutputStream(file)
            val buffer = ByteArray(1024)
            var len: Int
                while (((inputStream.read(buffer).also { len = it })) != -1) {
                    fos.write(buffer, 0, len)
                    }
              fos.flush()
                  inputStream.close()
                  fos.close()
              }

    打包MSI

    在Windows環境下打包Msi格式安裝包的時候,有一個downloadWix的Task,該Task涉及到了Wix資源的下載,如下 :

    Task :downloadWix Download 點擊下載

    在IDEA中下載可能會非常的緩慢,此時我們可以復制上述地址,登上梯子,然后直接去GitHub下載。下載完畢后直接放入【/build/wixToolset】目錄下即可,再次編譯速度就會起飛了。

    感謝各位的閱讀,以上就是“ComposeDesktop開發桌面端多功能APK工具怎么使用”的內容了,經過本文的學習后,相信大家對ComposeDesktop開發桌面端多功能APK工具怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    apk
    AI

    新民市| 夏河县| 茌平县| 读书| 高密市| 萨迦县| 于田县| 平凉市| 桐乡市| 青龙| 蒙自县| 渝北区| 许昌县| 长岭县| 阳曲县| 麻江县| 五莲县| 广河县| 彰武县| 焉耆| 永登县| 沅江市| 武清区| 连江县| 汶川县| 宽甸| 贡山| 桦甸市| 应用必备| 汉寿县| 都昌县| 青州市| 休宁县| 武乡县| 孟村| 奎屯市| 蓬溪县| 陆良县| 蕲春县| 南陵县| 高阳县|