主頁 > 移動端開發 > 微服務框架:如果不用Spring Boot,還可以選擇誰

微服務框架:如果不用Spring Boot,還可以選擇誰

2021-08-09 08:20:48 移動端開發

目錄

前言

先決條件

從頭開始創建應用程式

Helidon服務

Ktor服務

Micronaut 服務

Quarkus服務

Spring Boot服務

啟動微服務

API測驗

不同微服務框架對比

程式大小

啟動時長

記憶體使用情況

結論

Helidon標準版

Helidon MicroProfile

Ktor

Micronaut

Quarkus

Spring Boot


前言

在 Java 和 Kotlin 中, 除了使用Spring Boot創建微服務外,還有很多其他的替代方案,

名稱版本發布時間開發商GitHub
Helidon SE1.4.12019年甲骨文鏈接
Ktor1.3.02018年JetBrains鏈接
Micronaut1.2.92018年Object Computing鏈接
Quarkus1.2.02019年Red Hat鏈接
Spring Boot2.2.42014年Pivotal鏈接

本文,基于這些微服務框架,創建了五個服務,并使用Consul的服務發現模式實作服務間的 相互通信,因此,它們形成了異構微服務架構(Heterogeneous Microservice Architecture, 以下簡稱 MSA):

本文簡要考慮了微服務在各個框架上的實作(更多細節請查看源代碼:https : //github.com/rkudryashov/heterogeneous-microservices)

  • 技術堆疊:
    • JDK 13
    • Kotlin
    • Gradle (Kotlin DSL)
    • JUnit 5
  • 功能介面(HTTP API):
    • GET /application-info{?request-to=some-service-name}
      • -- 回傳微服務的一些基本資訊(名稱、框架、發布年份)
    • GET /application-info/logo
      • -- 回傳logo資訊
  • 實作方式:
    • 使用文本檔案的配置方式
    • 使用依賴注入
    • HTTP API
  • MSA:
    • 使用服務發現模式(在Consul中注冊,通過客戶端負載均衡的名稱請求另一個微服務的HTTP API)
    • 構建一個 uber-JAR

先決條件

  • JDK 13
  • Consul

從頭開始創建應用程式

要基于其中一個框架上生成新專案,你可以使用web starter 或其他選項(例如,構建工具或 IDE):

名稱Web starter指南支持的開發語言
Helidon鏈接(MP)鏈接(SE) 鏈接(MP)Java,Kotlin
Ktor鏈接鏈接Kotlin
Micronaut鏈接鏈接Groovy、Java、Kotlin
Quarkus鏈接鏈接Java、Kotlin、Scala
Spring Boot鏈接鏈接Groovy、Java、Kotlin

Helidon服務

該框架是在 Oracle 中創建以供內部使用,隨后成為開源,Helidon 非常簡單和快捷,它提供了兩個版本:標準版(SE)和MicroProfile(MP),在這兩種情況下,服務都是一個常規的 Java SE 程式,(在Helidon上了解更多資訊)

Helidon MP 是 Eclipse MicroProfile的實作之一,這使得使用許多 API 成為可能,包括 Java EE 開發人員已知的(例如 JAX-RS、CDI等)和新的 API(健康檢查、指標、容錯等),在 Helidon SE 模型中,開發人員遵循“沒有魔法”的原則,例如,創建應用程式所需的注解數量較少或完全沒有,

Helidon SE 被選中用于微服務的開發,因為Helidon SE 缺乏依賴注入的手段,因此為此使用了Koin,

以下代碼示例,是包含 main 方法的類,為了實作依賴注入,該類繼承自KoinComponent,

首先,Koin 啟動,然后初始化所需的依賴并呼叫startServer()方法----其中創建了一個WebServer型別的物件,應用程式配置和路由設定傳遞到該物件;

啟動應用程式后在Consul注冊:

object HelidonServiceApplication : KoinComponent {
?
    @JvmStatic
    fun main(args: Array<String>) {
        val startTime = System.currentTimeMillis()
        startKoin {
            modules(koinModule)
        }
?
        val applicationInfoService: ApplicationInfoService by inject()
        val consulClient: Consul by inject()
        val applicationInfoProperties: ApplicationInfoProperties by inject()
        val serviceName = applicationInfoProperties.name
?
        startServer(applicationInfoService, consulClient, serviceName, startTime)
    }
}
?
fun startServer(
    applicationInfoService: ApplicationInfoService,
    consulClient: Consul,
    serviceName: String,
    startTime: Long
): WebServer {
    val serverConfig = ServerConfiguration.create(Config.create().get("webserver"))
?
    val server: WebServer = WebServer
        .builder(createRouting(applicationInfoService))
        .config(serverConfig)
        .build()
?
    server.start().thenAccept { ws ->
        val durationInMillis = System.currentTimeMillis() - startTime
        log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())
        // register in Consul
        consulClient.agentClient().register(createConsulRegistration(serviceName, ws.port()))
    }
?
    return server
}

路由配置如下:

private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder()
    .register(JacksonSupport.create())
    .get("/application-info", Handler { req, res ->
        val requestTo: String? = req.queryParams()
            .first("request-to")
            .orElse(null)
?
        res
            .status(Http.ResponseStatus.create(200))
            .send(applicationInfoService.get(requestTo))
    })
    .get("/application-info/logo", Handler { req, res ->
        res.headers().contentType(MediaType.create("image", "png"))
        res
            .status(Http.ResponseStatus.create(200))
            .send(applicationInfoService.getLogo())
    })
    .error(Exception::class.java) { req, res, ex ->
        log.error("Exception:", ex)
        res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send()
    }
    .build()

該應用程式使用HOCON格式的組態檔:

webserver {
  port: 8081
}
?
application-info {
  name: "helidon-service"
  framework {
    name: "Helidon SE"
    release-year: 2019
  }
}

還可以使用 JSON、YAML 和properties 格式的檔案進行配置(在Helidon 配置檔案中了解更多資訊),

Ktor服務

該框架是為 Kotlin 撰寫和設計的,和 Helidon SE 一樣,Ktor 沒有開箱即用的 DI,所以在啟動服務器依賴項之前應該使用 Koin 注入:

val koinModule = module {
    single { ApplicationInfoService(get(), get()) }
    single { ApplicationInfoProperties() }
    single { ServiceClient(get()) }
    single { Consul.builder().withUrl("http://localhost:8500").build() }
}
?
fun main(args: Array<String>) {
    startKoin {
        modules(koinModule)
    }
    val server = embeddedServer(Netty, commandLineEnvironment(args))
    server.start(wait = true)
}

應用程式需要的模塊在組態檔中指定(HOCON格式;更多配置資訊參考Ktor配置檔案 ),其內容如下:

ktor {
  deployment {
    host = localhost
    port = 8082
    environment = prod
    // for dev purpose
    autoreload = true
    watch = [io.heterogeneousmicroservices.ktorservice]
  }
  application {
    modules = [io.heterogeneousmicroservices.ktorservice.module.KtorServiceApplicationModuleKt.module]
  }
}
?
application-info {
  name: "ktor-service"
  framework {
    name: "Ktor"
    release-year: 2018
  }
}

在 Ktor 和 Koin 中,術語“模塊”具有不同的含義,

在 Koin 中,模塊類似于 Spring 框架中的應用程式背景關系,Ktor的模塊是一個用戶定義的函式,它接受一個 Application型別的物件,可以配置流水線、注冊路由、處理請求等:

fun Application.module() {
    val applicationInfoService: ApplicationInfoService by inject()
?
    if (!isTest()) {
        val consulClient: Consul by inject()
        registerInConsul(applicationInfoService.get(null).name, consulClient)
    }
?
    install(DefaultHeaders)
    install(Compression)
    install(CallLogging)
    install(ContentNegotiation) {
        jackson {}
    }
?
    routing {
        route("application-info") {
            get {
                val requestTo: String? = call.parameters["request-to"]
                call.respond(applicationInfoService.get(requestTo))
            }
            static {
                resource("/logo", "logo.png")
            }
        }
    }
}

此代碼是配置請求的路由,特別是靜態資源logo.png,

下面是基于Round-robin演算法結合客戶端負載均衡實作服務發現模式的代碼:

class ConsulFeature(private val consulClient: Consul) {
?
    class Config {
        lateinit var consulClient: Consul
    }
?
    companion object Feature : HttpClientFeature<Config, ConsulFeature> {
        var serviceInstanceIndex: Int = 0
?
        override val key = AttributeKey<ConsulFeature>("ConsulFeature")
?
        override fun prepare(block: Config.() -> Unit) = ConsulFeature(Config().apply(block).consulClient)
?
        override fun install(feature: ConsulFeature, scope: HttpClient) {
            scope.requestPipeline.intercept(HttpRequestPipeline.Render) {
                val serviceName = context.url.host
                val serviceInstances =
                    feature.consulClient.healthClient().getHealthyServiceInstances(serviceName).response
                val selectedInstance = serviceInstances[serviceInstanceIndex]
                context.url.apply {
                    host = selectedInstance.service.address
                    port = selectedInstance.service.port
                }
                serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
            }
        }
    }
}

主要邏輯在install方法中:在Render請求階段(在Send階段之前執行)首先確定被呼叫服務的名稱,然后consulClient請求服務的實體串列,然后通過回圈演算法定義一個實體正在呼叫,因此,以下呼叫成為可能:

fun getApplicationInfo(serviceName: String): ApplicationInfo = runBlocking {
    httpClient.get<ApplicationInfo>("http://$serviceName/application-info")
}

Micronaut 服務

Micronaut 由Grails框架的創建者開發,靈感來自使用 Spring、Spring Boot 和 Grails 構建服務的經驗,該框架目前支持 Java、Kotlin 和 Groovy 語言,依賴是在編譯時注入的,與 Spring Boot 相比,這會導致更少的記憶體消耗和更快的應用程式啟動,

主類如下所示:

object MicronautServiceApplication {
?
    @JvmStatic
    fun main(args: Array<String>) {
        Micronaut.build()
            .packages("io.heterogeneousmicroservices.micronautservice")
            .mainClass(MicronautServiceApplication.javaClass)
            .start()
    }
}

基于 Micronaut 的應用程式的某些組件與它們在 Spring Boot 應用程式中的對應組件類似,例如,以下是控制器代碼:

@Controller(
    value = "/application-info",
    consumes = [MediaType.APPLICATION_JSON],
    produces = [MediaType.APPLICATION_JSON]
)
class ApplicationInfoController(
    private val applicationInfoService: ApplicationInfoService
) {
?
    @Get
    fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)
?
    @Get("/logo", produces = [MediaType.IMAGE_PNG])
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
}

Micronaut 中對 Kotlin 的支持建立在kapt編譯器插件的基礎上(參考Micronaut Kotlin 指南了解更多詳細資訊),

構建腳本配置如下:

plugins {
    ...
    kotlin("kapt")
    ...
}
?
dependencies {
    kapt("io.micronaut:micronaut-inject-java:$micronautVersion")
    ...
    kaptTest("io.micronaut:micronaut-inject-java:$micronautVersion")
    ...
}

以下是組態檔的內容:

micronaut:
  application:
    name: micronaut-service
  server:
    port: 8083
?
consul:
  client:
    registration:
      enabled: true
?
application-info:
  name: ${micronaut.application.name}
  framework:
    name: Micronaut
    release-year: 2018

JSON、properties和 Groovy 檔案格式也可用于配置(參考Micronaut 配置指南查看更多詳細資訊),

Quarkus服務

Quarkus是作為一種應對新部署環境和應用程式架構等挑戰的工具而引入的,在框架上撰寫的應用程式將具有低記憶體消耗和更快的啟動時間,此外,對開發人員也很友好,例如,開箱即用的實時重新加載,

Quarkus 應用程式目前沒有 main 方法,但也許未來會出現(GitHub 上的問題),

對于熟悉 Spring 或 Java EE 的人來說,Controller 看起來非常熟悉:

@Path("/application-info")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class ApplicationInfoResource(
    @Inject private val applicationInfoService: ApplicationInfoService
) {
?
    @GET
    fun get(@QueryParam("request-to") requestTo: String?): Response =
        Response.ok(applicationInfoService.get(requestTo)).build()
?
    @GET
    @Path("/logo")
    @Produces("image/png")
    fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
}

如你所見,bean 是通過@Inject注解注入的,對于注入的 bean,你可以指定一個范圍,例如:

@ApplicationScoped
class ApplicationInfoService(
    ...
) {
...
}

為其他服務創建 REST 介面,就像使用 JAX-RS 和 MicroProfile 創建介面一樣簡單:

@ApplicationScoped
@Path("/")
interface ExternalServiceClient {
    @GET
    @Path("/application-info")
    @Produces("application/json")
    fun getApplicationInfo(): ApplicationInfo
}
?
@RegisterRestClient(baseUri = "http://helidon-service")
interface HelidonServiceClient : ExternalServiceClient
?
@RegisterRestClient(baseUri = "http://ktor-service")
interface KtorServiceClient : ExternalServiceClient
?
@RegisterRestClient(baseUri = "http://micronaut-service")
interface MicronautServiceClient : ExternalServiceClient
?
@RegisterRestClient(baseUri = "http://quarkus-service")
interface QuarkusServiceClient : ExternalServiceClient
?
@RegisterRestClient(baseUri = "http://spring-boot-service")
interface SpringBootServiceClient : ExternalServiceClient

但是它現在缺乏對服務發現 ( Eureka和Consul ) 的內置支持,因為該框架主要針對云環境,因此,在 Helidon 和 Ktor 服務中, 我使用了Java類別庫方式的Consul 客戶端,

首先,需要注冊應用程式:

@ApplicationScoped
class ConsulRegistrationBean(
    @Inject private val consulClient: ConsulClient
) {
?
    fun onStart(@Observes event: StartupEvent) {
        consulClient.register()
    }
}

然后需要將服務的名稱決議到其特定位置;

決議是通過從 Consul 客戶端獲得的服務的位置替換 requestContext的URI 來實作的:

@Provider
@ApplicationScoped
class ConsulFilter(
    @Inject private val consulClient: ConsulClient
) : ClientRequestFilter {
?
    override fun filter(requestContext: ClientRequestContext) {
        val serviceName = requestContext.uri.host
        val serviceInstance = consulClient.getServiceInstance(serviceName)
        val newUri: URI = URIBuilder(URI.create(requestContext.uri.toString()))
            .setHost(serviceInstance.address)
            .setPort(serviceInstance.port)
            .build()
?
        requestContext.uri = newUri
    }
}

Quarkus也支持通過properties 或 YAML 檔案進行配置(參考Quarkus 配置指南了解更多詳細資訊),

Spring Boot服務

創建該框架是為了使用 Spring Framework 生態系統,同時有利于簡化應用程式的開發,這是通過auto-configuration實作的,

以下是控制器代碼:

@RestController
@RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_VALUE])
class ApplicationInfoController(
    private val applicationInfoService: ApplicationInfoService
) {
?
    @GetMapping
    fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)
?
    @GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE])
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
}

微服務由 YAML 檔案配置:

spring:
  application:
    name: spring-boot-service
?
server:
  port: 8085
?
application-info:
  name: ${spring.application.name}
  framework:
    name: Spring Boot
    release-year: 2014

也可以使用properties檔案進行配置(更多資訊參考Spring Boot 配置檔案),

啟動微服務

在啟動微服務之前,你需要安裝Consul和 啟動代理-例如,像這樣:consul agent -dev,

你可以從以下位置啟動微服務:

  • IDE中啟動微服務IntelliJ IDEA 的用戶可能會看到如下內容:

    要啟動 Quarkus 服務,你需要啟動quarkusDev的Gradle 任務,
  • console中啟動微服務在專案的根檔案夾中執行:

java -jar helidon-service/build/libs/helidon-service-all.jar

java -jar ktor-service/build/libs/ktor-service-all.jar

java -jar micronaut-service/build/libs/micronaut-service-all.jar

java -jar quarkus-service/build/quarkus-service-1.0.0-runner.jar

java -jar spring-boot-service/build/libs/spring-boot-service.jar

啟動所有微服務后,訪問http://localhost:8500/ui/dc1/services,你將看到:

API測驗

以Helidon服務的API測驗結果為例:

  • GET http://localhost:8081/application-info
    {
      "name": "helidon-service",
      "framework": {
        "name": "Helidon SE",
        "releaseYear": 2019
      },
      "requestedService": null
    }
  • GET http://localhost:8081/application-info?request-to=ktor-service
    {
      "name": "helidon-service",
      "framework": {
        "name": "Helidon SE",
        "releaseYear": 2019
      },
      "requestedService": {
        "name": "ktor-service",
        "framework": {
              "name": "Ktor",
              "releaseYear": 2018
        },
        "requestedService": null
      }
    }
  • GET http://localhost:8081/application-info/logo回傳logo資訊

你可以使用Postman 、IntelliJ IDEA HTTP 客戶端 、瀏覽器或其他工具測驗微服務的 API介面 ,

不同微服務框架對比

不同微服務框架的新版本發布后,下面的結果可能會有變化;你可以使用此GitHub專案自行檢查最新的對比結果 ,

程式大小

為了保證設定應用程式的簡單性,構建腳本中沒有排除傳遞依賴項,因此 Spring Boot 服務 uber-JAR 的大小大大超過了其他框架上的類似物的大小(因為使用 starters 不僅匯入了必要的依賴項;如果需要,可以通過排除指定依賴來減小大小):

備注:什么是 maven的uber-jar

在maven的一些檔案中我們會發現 "uber-jar"這個術語,許多人看到后感到困惑,其實在很多編程語言中會把super叫做uber (因為super可能是關鍵字), 這是上世紀80年代開始流行的,比如管superman叫uberman,所以uber-jar從字面上理解就是super-jar,這樣的jar不但包含自己代碼中的class ,也會包含一些第三方依賴的jar,也就是把自身的代碼和其依賴的jar全打包在一個jar里面了,所以就很形象的稱其為super-jar ,uber-jar來歷就是這樣的,

微服務程式大小(MB)
Helidon服務17,3
Ktor服務22,4
Micronaut 服務17,1
Quarkus服務24,4
Spring Boot服務45,2

啟動時長

每個應用程式的啟動時長都是不固定的:

微服務開始時間(秒)
Helidon服務2,0
Ktor服務1,5
Micronaut 服務2,8
Quarkus服務1,9
Spring Boot服務10,7

值得注意的是,如果你將 Spring Boot 中不必要的依賴排除,并注意設定應用的啟動引數(例如,只掃描必要的包并使用 bean 的延遲初始化),那么你可以顯著地減少啟動時間,

記憶體使用情況

對于每個微服務,確定了以下內容:

  • 通過-Xmx引數,指定微服務所需的堆記憶體大小
  • 通過負載測驗服務健康的請求(能夠回應不同的請求)
  • 通過負載測驗50 個用戶 * 1000 個的請求
  • 通過負載測驗500 個用戶 * 1000 個的請求

堆記憶體只是為應用程式分配的總記憶體的一部分,例如,如果要測量總體記憶體使用情況,可以參考本指南,

對于負載測驗,使用了Gatling和Scala腳本 ,

  • 負載生成器和被測驗的服務在同一臺機器上運行(Windows 10、3.2 GHz 四核處理器、24 GB RAM、SSD),
  • 服務的埠在 Scala 腳本中指定,
  • 通過負載測驗意味著微服務已經回應了所有時間的所有請求,
微服務堆記憶體大小(MB)堆記憶體大小(MB)堆記憶體大小(MB)
對于健康服務對于 50 * 1000 的負載對于 500 * 1000 的負載
Helidon服務11911
Ktor服務131115
Micronaut 服務171519
Quarkus服務131721
Spring Boot服務181923

需要注意的是,所有微服務都使用 Netty HTTP 服務器,

結論

通過上文,我們所需的功能——一個帶有 HTTP API 的簡單服務和在 MSA 中運行的能力——在所有考慮的框架中都取得了成功,

是時候開始盤點并考慮他們的利弊了,

Helidon標準版

優點

創建的應用程式,只需要一個注釋(@JvmStatic)

缺點

開發所需的一些組件缺少開箱即用(例如,依賴注入和與服務發現服務器的互動)

Helidon MicroProfile

微服務還沒有在這個框架上實作,所以這里簡單說明一下,

優點

Eclipse MicroProfile 實作

本質上,MicroProfile 是針對 MSA 優化的 Java EE,因此,首先你可以訪問各種 Java EE API,包括專門為 MSA 開發的 API,其次,你可以將 MicroProfile 的實作更改為任何其他實作(例如:Open Liberty、WildFly Swarm 等)

Ktor

優點

  • 輕量級的允許你僅添加執行任務直接需要的那些功能
  • 應用引數所有引數的良好結果

缺點

  • 依賴于Kotlin,即用其他語言開發可能是不可能的或不值得的
  • 微框架:參考Helidon SE
  • 目前最流行的兩種 Java 開發模型(Spring Boot/Micronaut)和 Java EE/MicroProfile)中沒有包含該框架,這會導致:
    • 難以尋找專家
    • 由于需要顯式配置所需的功能,因此與 Spring Boot 相比,執行任務的時間有所增加

Micronaut

優點

  • AOT如前所述,與 Spring Boot 上的模擬相比,AOT 可以減少應用程式的啟動時間和記憶體消耗
  • 類Spring開發模式有 Spring 框架經驗的程式員不會花太多時間來掌握這個框架
  • Micronaut for Spring可以改變現有的Spring Boot應用程式的執行環境到Micronaut中(有限制)

Quarkus

優點

  • Eclipse MicroProfile 的實作
  • 該框架為多種 Spring 技術提供了兼容層:DI、 Web、Security、Data JPA

Spring Boot

優點

  • 平臺成熟度和生態系統對于大多數日常任務,Spring的編程范式已經有了解決方案,也是很多程式員習慣的方式,此外,starter和auto-configuration的概念簡化了開發
  • 專家多,檔案詳細

我想很多人都會同意 Spring 在不久的將來仍將是 Java/Kotlin開發領域領先的框架,

缺點

  • 應用引數多且復雜但是,有些引數,如前所述,你可以自己優化,還有一個Spring Fu專案的存在,該專案正在積極開發中,使用它可以減少引數,

Helidon SE 和 Ktor 是 微框架,Spring Boot 和 Micronaut 是全堆疊框架,Quarkus 和 Helidon MP 是 MicroProfile 框架,微框架的功能有限,這會減慢開發速度,

我不敢判斷這個或那個框架會不會在近期“大更新”,所以在我看來,目前最好繼續觀察,使用熟悉的框架解決作業問題,

同時,如本文所示,新框架在應用程式引數設定方面贏得了 Spring Boot,如果這些引數中的任何一個對你的某個微服務至關重要,那么也許值得關注,但是,我們不要忘記,Spring Boot 一是在不斷改進,二是它擁有龐大的生態系統,并且有相當多的 Java 程式員熟悉它,此外,還有未涉及的其他框架:Vert.x、Javalin 等,也值得關注,

參考鏈接: https://dzone.com/articles/not-only-spring-boot-a-review-of-alternatives

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/292531.html

標籤:其他

上一篇:Flutter 構建一個完整的聊天應用程式

下一篇:使用 Flutter 制作一個簡單的笑話生成器應用程式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more