M8Test Help

开发项目

本页面介绍如何一步步开发一个M8Test脚本项目。

简单项目

  1. 创建 settings.config.yaml 文件,内容如下

language: name: "Kotlin" url: "https://github.com/m8test/language-release/releases/download/kotlin-0.1.0/com.m8test.kotlin-release_0.1.0.apk" version: "0.1.0"
language: name: "Groovy" url: "https://github.com/m8test/language-release/releases/download/groovy-0.1.0/com.m8test.groovy-release_0.1.0.apk" version: "0.1.0"
language: name: "Javascript" url: "https://github.com/m8test/language-release/releases/download/javascript-0.1.0/com.m8test.javascript.v8-release_0.1.0.apk" version: "0.1.0"
language: name: "Lua" url: "https://github.com/m8test/language-release/releases/download/lua-0.1.0/com.m8test.lua-release_0.1.0.apk" version: "0.1.0"
language: name: "Php" url: "https://github.com/m8test/language-release/releases/download/php-0.1.0/com.m8test.php-release_0.1.0.apk" version: "0.1.0"
language: name: "Python" url: "https://github.com/m8test/language-release/releases/download/python-0.1.0/com.m8test.python-release_0.1.0.apk" version: "0.1.0"
language: name: "Ruby" url: "https://github.com/m8test/language-release/releases/download/ruby-0.1.0/com.m8test.ruby-release_0.1.0.apk" version: "0.1.0"

其中指定了执行settingsinit.settings文件的脚本语言名称、下载地址及版本。

  1. 创建 settings 文件,每种语言所需的扩展名不同

这里简单打印一句话。若包含子项目,可在此文件中配置。一个项目只需一个 settings 文件,子项目中无需该文件。

  1. 创建 build 文件,每种语言所需的扩展名不同

_project.getPlugins().apply("kotlin") _project.setConfig { setEntry("com/example/script/primary.kts") setPackageName("com.example.script.kotlin") setLogo("logo.png") }
$project.getPlugins().apply("groovy") $project.setConfig { setEntry("com/example/script/primary.groovy") setPackageName("com.example.script.groovy") setLogo("logo.png") }
$project.getPlugins().apply("javascript") $project.setConfig(config => { config.setEntry("com/example/script/primary.js") config.setPackageName("com.example.script.js") config.setLogo("logo.png") })
_project:getPlugins():apply("lua") _project:setConfig(function(c) c:setEntry("com/example/script/primary.lua") c:setPackageName("com.example.script.lua") c:setLogo("logo.png") end)
<?php $project->getPlugins()->apply("php"); $project->setConfig(function ($config) { $config->setEntry("com/example/script/primary.php"); $config->setPackageName("com.example.script.php"); $config->setLogo("logo.png"); });
_project.getPlugins().apply("python") def configuration(config): config.setEntry("com/example/script/primary.py") config.setPackageName("com.example.script.py") config.setLogo("logo.png") _project.setConfig(configuration)
$project.getPlugins().apply("ruby") $project.setConfig { |config| config.setEntry("com/example/script/primary.rb") config.setPackageName("com.example.script.rb") config.setLogo("logo.png") }

其中配置语言插件及项目基本信息(脚本入口文件、包名及logo)。

  1. 创建 src 目录用于存放脚本源码,新建上一步骤配置的入口文件

  1. 创建 res 目录,并添加第3步配置的logo文件,即可完成最简单的项目。

带UI的项目

  1. 创建 webview 目录用于存放UI相关文件,该目录实际用于存放静态网站内容。可显示任意前端框架(vue、angular、react等)的编译产物,也可以是简单的 index.html 文件。这里演示简单网站,仅包含两个文件: index.htmlindex.js

index.html 内容如下,非常简单,仅引用了 index.js 文件:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView UI</title> <link href="https://fastly.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> <script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> </head> <body> <button id="button" type="button" class="btn btn-primary">Primary</button> <script src="./index.js"></script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView UI</title> <link href="https://fastly.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> <script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> </head> <body> <button id="button" type="button" class="btn btn-primary">Primary</button> <script src="./index.js"></script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView UI</title> <link href="https://fastly.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> <script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> </head> <body> <button id="button" type="button" class="btn btn-primary">Primary</button> <script src="./index.js"></script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView UI</title> <link href="https://fastly.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> <script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> </head> <body> <button id="button" type="button" class="btn btn-primary">Primary</button> <script src="./index.js"></script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView UI</title> <link href="https://fastly.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> <script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> </head> <body> <button id="button" type="button" class="btn btn-primary">Primary</button> <script src="./index.js"></script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView UI</title> <link href="https://fastly.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> <script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> </head> <body> <button id="button" type="button" class="btn btn-primary">Primary</button> <script src="./index.js"></script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView UI</title> <link href="https://fastly.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> <script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> </head> <body> <button id="button" type="button" class="btn btn-primary">Primary</button> <script src="./index.js"></script> </body> </html>

index.js 内容如下:

(function () { function init() { // $webViewBridge.registerHandler() 用于设置供脚本端调用的处理器,第一个参数是处理器名,第二个参数是一个函数,会在处理器被调用时执行 $webViewBridge.registerHandler("functionForScriptToCall", function (data, responseCallback) { // data 为脚本端调用处理器时传递过来的参数 // responseCallback 用于返回处理结果到脚本端 if (responseCallback) { var responseData = "I am from Javascript " + data responseCallback(responseData) } }) } if (window.$webViewBridge) { init(); } else { document.addEventListener( 'WebViewBridgeReady', function () { //do your work here init(); }, false ); } })() document.getElementById("button").addEventListener("click", function (e) { // $webViewBridge.callHandler() 用于调用脚本端注册的处理器,参数一为处理器名,参数二为需要传递给处理器的参数,参数三为回调函数用于接收脚本端的返回值 $webViewBridge.callHandler("handlerForWebView", "params from js", function (p) { alert(p) }) })
(function () { function init() { // $webViewBridge.registerHandler() 用于设置供脚本端调用的处理器,第一个参数是处理器名,第二个参数是一个函数,会在处理器被调用时执行 $webViewBridge.registerHandler("functionForScriptToCall", function (data, responseCallback) { // data 为脚本端调用处理器时传递过来的参数 // responseCallback 用于返回处理结果到脚本端 if (responseCallback) { var responseData = "I am from Javascript " + data responseCallback(responseData) } }) } if (window.$webViewBridge) { init(); } else { document.addEventListener( 'WebViewBridgeReady', function () { //do your work here init(); }, false ); } })() document.getElementById("button").addEventListener("click", function (e) { // $webViewBridge.callHandler() 用于调用脚本端注册的处理器,参数一为处理器名,参数二为需要传递给处理器的参数,参数三为回调函数用于接收脚本端的返回值 $webViewBridge.callHandler("handlerForWebView", "params from js", function (p) { alert(p) }) })
(function () { function init() { // $webViewBridge.registerHandler() 用于设置供脚本端调用的处理器,第一个参数是处理器名,第二个参数是一个函数,会在处理器被调用时执行 $webViewBridge.registerHandler("functionForScriptToCall", function (data, responseCallback) { // data 为脚本端调用处理器时传递过来的参数 // responseCallback 用于返回处理结果到脚本端 if (responseCallback) { var responseData = "I am from Javascript " + data responseCallback(responseData) } }) } if (window.$webViewBridge) { init(); } else { document.addEventListener( 'WebViewBridgeReady', function () { //do your work here init(); }, false ); } })() document.getElementById("button").addEventListener("click", function (e) { // $webViewBridge.callHandler() 用于调用脚本端注册的处理器,参数一为处理器名,参数二为需要传递给处理器的参数,参数三为回调函数用于接收脚本端的返回值 $webViewBridge.callHandler("handlerForWebView", "params from js", function (p) { alert(p) }) })
(function () { function init() { // $webViewBridge.registerHandler() 用于设置供脚本端调用的处理器,第一个参数是处理器名,第二个参数是一个函数,会在处理器被调用时执行 $webViewBridge.registerHandler("functionForScriptToCall", function (data, responseCallback) { // data 为脚本端调用处理器时传递过来的参数 // responseCallback 用于返回处理结果到脚本端 if (responseCallback) { var responseData = "I am from Javascript " + data responseCallback(responseData) } }) } if (window.$webViewBridge) { init(); } else { document.addEventListener( 'WebViewBridgeReady', function () { //do your work here init(); }, false ); } })() document.getElementById("button").addEventListener("click", function (e) { // $webViewBridge.callHandler() 用于调用脚本端注册的处理器,参数一为处理器名,参数二为需要传递给处理器的参数,参数三为回调函数用于接收脚本端的返回值 $webViewBridge.callHandler("handlerForWebView", "params from js", function (p) { alert(p) }) })
(function () { function init() { // $webViewBridge.registerHandler() 用于设置供脚本端调用的处理器,第一个参数是处理器名,第二个参数是一个函数,会在处理器被调用时执行 $webViewBridge.registerHandler("functionForScriptToCall", function (data, responseCallback) { // data 为脚本端调用处理器时传递过来的参数 // responseCallback 用于返回处理结果到脚本端 if (responseCallback) { var responseData = "I am from Javascript " + data responseCallback(responseData) } }) } if (window.$webViewBridge) { init(); } else { document.addEventListener( 'WebViewBridgeReady', function () { //do your work here init(); }, false ); } })() document.getElementById("button").addEventListener("click", function (e) { // $webViewBridge.callHandler() 用于调用脚本端注册的处理器,参数一为处理器名,参数二为需要传递给处理器的参数,参数三为回调函数用于接收脚本端的返回值 $webViewBridge.callHandler("handlerForWebView", "params from js", function (p) { alert(p) }) })
(function () { function init() { // $webViewBridge.registerHandler() 用于设置供脚本端调用的处理器,第一个参数是处理器名,第二个参数是一个函数,会在处理器被调用时执行 $webViewBridge.registerHandler("functionForScriptToCall", function (data, responseCallback) { // data 为脚本端调用处理器时传递过来的参数 // responseCallback 用于返回处理结果到脚本端 if (responseCallback) { var responseData = "I am from Javascript " + data responseCallback(responseData) } }) } if (window.$webViewBridge) { init(); } else { document.addEventListener( 'WebViewBridgeReady', function () { //do your work here init(); }, false ); } })() document.getElementById("button").addEventListener("click", function (e) { // $webViewBridge.callHandler() 用于调用脚本端注册的处理器,参数一为处理器名,参数二为需要传递给处理器的参数,参数三为回调函数用于接收脚本端的返回值 $webViewBridge.callHandler("handlerForWebView", "params from js", function (p) { alert(p) }) })
(function () { function init() { // $webViewBridge.registerHandler() 用于设置供脚本端调用的处理器,第一个参数是处理器名,第二个参数是一个函数,会在处理器被调用时执行 $webViewBridge.registerHandler("functionForScriptToCall", function (data, responseCallback) { // data 为脚本端调用处理器时传递过来的参数 // responseCallback 用于返回处理结果到脚本端 if (responseCallback) { var responseData = "I am from Javascript " + data responseCallback(responseData) } }) } if (window.$webViewBridge) { init(); } else { document.addEventListener( 'WebViewBridgeReady', function () { //do your work here init(); }, false ); } })() document.getElementById("button").addEventListener("click", function (e) { // $webViewBridge.callHandler() 用于调用脚本端注册的处理器,参数一为处理器名,参数二为需要传递给处理器的参数,参数三为回调函数用于接收脚本端的返回值 $webViewBridge.callHandler("handlerForWebView", "params from js", function (p) { alert(p) }) })

需要注意以下几点:

  • $webViewBridge.registerHandler:在网页端注册处理器,注册后脚本端可调用已注册的处理器

  • $webViewBridge.callHandler:用于调用脚本端注册的处理器

  1. 脚本端(src )内容如下:

package com.example.script // WebViewActivity.getBridge() 用于获取 WebViewBridge 对象 val bridge = _webView.getBridge() // WebViewBridge.registerHandler() 用于注册处理器供 WebView 端调用 bridge.registerHandler("handlerForWebView") { // it -> String // it 表示从 WebView 端传来的参数,如果没有则为 null val handledParams = it + " return from script" // WebViewBridge.callHandler() 用于调用 WebView 端注册的处理器 bridge.callHandler("functionForScriptToCall", handledParams) { i -> // it -> String // it 为 WebView 端js函数返回值 _console.log(i) } handledParams } // Activity.start() 用于启动Android系统的Activity _activity.start()
package com.example.script // WebViewActivity.getBridge() 用于获取 WebViewBridge 对象 def bridge = $webView.getBridge() // WebViewBridge.registerHandler() 用于注册处理器供 WebView 端调用 bridge.registerHandler("handlerForWebView") { // it -> String // it 表示从 WebView 端传来的参数,如果没有则为 null def handledParams = "$it return from script" // WebViewBridge.callHandler() 用于调用 WebView 端注册的处理器 bridge.callHandler("functionForScriptToCall", handledParams) { // it -> String // it 为 WebView 端js函数返回值 $console.log(it) } handledParams } // Activity.start() 用于启动Android系统的Activity $activity.start()
// WebViewActivity.getBridge() 用于获取 WebViewBridge 对象 let bridge = $webView.getBridge() // WebViewBridge.registerHandler() 用于注册处理器供 WebView 端调用 bridge.registerHandler("handlerForWebView", (it) => { // it -> String // it 表示从 WebView 端传来的参数,如果没有则为 null let handledParams = it + " return from script" // WebViewBridge.callHandler() 用于调用 WebView 端注册的处理器 bridge.callHandler("functionForScriptToCall", handledParams, (it) => { // it -> String // it 为 WebView 端js函数返回值 $console.log(it) }) return handledParams }) // Activity.start() 用于启动Android系统的Activity $activity.start()
-- WebViewActivity.getBridge() 用于获取 WebViewBridge 对象 local bridge = _webView:getBridge() -- WebViewBridge.registerHandler() 用于注册处理器供 WebView 端调用 bridge:registerHandler("handlerForWebView", function(it) -- it -> String -- it 表示从 WebView 端传来的参数,如果没有则为 nil local handledParams = it .. "return from script" -- WebViewBridge.callHandler() 用于调用 WebView 端注册的处理器 bridge:callHandler("functionForScriptToCall", handledParams, function(i) -- it -> String -- it 为 WebView 端js函数返回值 _console:log(i) end) return handledParams end) -- Activity.start() 用于启动Android系统的Activity _activity:start()
<?php // WebViewActivity.getBridge() 用于获取 WebViewBridge 对象 $bridge = $webView->getBridge(); // WebViewBridge.registerHandler() 用于注册处理器供 WebView 端调用 $bridge->registerHandler("handlerForWebView", function ($it) use ($console, $bridge) { // it -> String // it 表示从 WebView 端传来的参数,如果没有则为 nil $handledParams = $it . "return from script"; // WebViewBridge.callHandler() 用于调用 WebView 端注册的处理器 $bridge->callHandler("functionForScriptToCall", $handledParams, function ($i) use ($console) { // it -> String // it 为 WebView 端js函数返回值 $console->log($i); }); return $handledParams; }); // Activity.start() 用于启动Android系统的Activity $activity->start();
# WebViewActivity.getBridge() 用于获取 WebViewBridge 对象 bridge = _webView.getBridge() def fn1(it): # it -> String # it 表示从 WebView 端传来的参数,如果没有则为 null handledParams = it + " return from script" def fn2(i): # it -> String # it 为 WebView 端js函数返回值 _console.log(i) # WebViewBridge.callHandler() 用于调用 WebView 端注册的处理器 bridge.callHandler("functionForScriptToCall", handledParams, fn2) return handledParams # WebViewBridge.registerHandler() 用于注册处理器供 WebView 端调用 bridge.registerHandler("handlerForWebView", fn1) # Activity.start() 用于启动Android系统的Activity _activity.start()
# WebViewActivity.getBridge() 用于获取 WebViewBridge 对象 bridge = $webView.getBridge() # WebViewBridge.registerHandler() 用于注册处理器供 WebView 端调用 bridge.registerHandler("handlerForWebView") { |it| # it -> String # it 表示从 WebView 端传来的参数,如果没有则为 null handledParams = it + "return from script" # WebViewBridge.callHandler() 用于调用 WebView 端注册的处理器 bridge.callHandler("functionForScriptToCall", handledParams) { |i| # it -> String # it 为 WebView 端js函数返回值 $console.log(i) } handledParams } # Activity.start() 用于启动Android系统的Activity $activity.start()

需要注意以下几点:

  • bridge.registerHandler:用于在脚本端注册处理器,注册的处理器可在网页端调用

  • bridge.callHandler:用于调用网页端已注册的处理器

09 December 2025