V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  murmurkerman  ›  全部回复第 1 页 / 共 2 页
回复总数  24
1  2  
49 天前
回复了 wchluxi 创建的主题 问与答 大家有试过在安卓 pad 用 moonlight 吗?
这用来办公(打游戏)还不如用 steam remote play 。
如果只需要加密文件,不希望其他应用访问到,用谷歌的文件极客,需要输入密码才能访问。
如果只是查看文件到的话,wps 有个 xxx 文档查看器,OEM 定制的,可以打开 office 文档,但是没有历史页面。https://apkcombo.com/zh/%E5%B0%8F%E7%B1%B3%E6%96%87%E6%A1%A3%E6%9F%A5%E7%9C%8B%E5%99%A8-wps%E5%AE%9A%E5%88%B6/cn.wps.moffice_eng.xiaomi.lite/
这个是好的,加上 key 来保留滚动位置

@Preview
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun VerticalPagerWithLazyColumn(
modifier: Modifier = Modifier
) {
val pagerState = rememberPagerState(
pageCount = {
5
}
)

VerticalPager(
state = pagerState,
key = { it },
modifier = modifier.fillMaxSize()
) { page ->
Box {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxSize()

) {
for (i in 0..20) {
Text(
"Item $i on page $page",
modifier = Modifier.padding(30.dp)
)
}
}
Text(
"Page $page", modifier = Modifier
.padding(16.dp)
.align(Alignment.Center)
)
}
}
}
啊,这个不是好的么
63 天前
回复了 koor 创建的主题 问与答 穿短裤怎么可以让腿不晒黑?
当然是打伞
这个明显要用增量更新,可以看看有没有类似于 remote config 的国内替代。没有的话只能自己撸一个。也可以借助 s3 ,oss 实现,s3 文件下载都会返回一个文件内容的 hash 值,可以用 head 请求判断是否更新了。然后就是拉配置的时候需要有一个请求队列,安卓 okhttp 自带,不用担心网络并发。

配置下载也可以加一个分页接口,一次下载 n 项。
最好还是用 protobuff 缩小配置文件传输大小。
哈哈哈,假如一开始 Flutter 就是一个商业 SDK ,从 2015 年开始到 2018 第一个移动双端稳定版本,这个项目对开发者来说其实没有任何工程价值。
何况开源免费的 React Native 已经从 2015 年稳定双端了。至少到 2018 年前 Flutter 毫无竞争力。

没有开源社区的支持,Flutter 很难发展到现在这样。实际上大量的代码都是有非谷歌员工贡献的。

对于 Google 来说将 Flutter 商业化带来的价值微乎其微。将其开源可以受益内部项目,减少研发投入。

其次 Google 并没有开源所有系统,基于 Android 的 Android Thing 是闭源的,到现在 Android thing 并不成功,没有多少市场占用。

反而 Android Tv ,Android Watch Os ,是核心系统开源,谷歌软件闭源。这两个到是有广泛的市场占用。
挺好的,就是一些 ui 组件可能不符合你们自己的要去,要复制官方代码修改。包括 switch ,menu ,tab 之类的。导航组件目前支持了 safeargs ,对话框存在一些显示问题。其它都挺好的包括与 view 系统的集成,嵌套滚动之类的。
86 天前
回复了 cx2ex 创建的主题 Android 如何说服老板使用原生而不是混合开发 APP
取决于有多少资源,和收益。首先要明确原生和混合方案的优缺点,显然原生之后可以迭代混合方案进来。至于 cordova ,你得评估下社区支持程度,比较流行的是 flutter 和 react native 。然后原生也需要选择 view 还是 compose 新架构意味着新的挑战。
评估好了之后再去招老板商讨人力资源能够协调多少,如果原生腾不出来你就只能选择混合了。其次鉴于是一个贷款类重营销的应用,我觉得迟早有部分 ui 需要动态下发,和如家国内的电商应用一样会需要嵌入混合开发
最重要的是数据要和 UI 解耦,无论你用 Android Service 还是普通类。和后端的 MVC 类似,Dao 、Connection Pool 是全局对象,Controller 只是用于处理用户交互,更新用户 UI 状态。你需要将自己的 ws 业务逻辑抽象到服务层中,viewmodel 负责从服务层获取数据,处理交互事件,service 层管理连接,发送接收处理消息。

至于是否使用 Android Service ,取决于你的应用是否需要在应用界面后继续运行,你还希望服务继续运行直到系统终止服务。一般情况下,例如媒体播放、录音、推送等及时性要起高的需要放到服务中。

一般只需要用普通的类来管理,

至于 ws 数据传输,ws 一般只用于同步状态:
1. 例如多人协作文档,需要同步输入位置,锁定编辑区域。
2. 大型二进制,例如文件、图片、音频,建议分开。
3. 实时翻译等,短时低延迟要求等,使用 ws 传输数据。

下面的示例中将没有处理消息放到了一个 ShardFlow 中,UI 收集这些数据,YourService 负责管理连接创建、关闭,ServiceConnection 处理消息通讯。

class YourApplication : Application() {

lateinit var yourService: YourService

override fun onCreate() {
super.onCreate()
yourService = YourService()
}
}

// 这里用 Application 类管理全局依赖
val Context.application: YourApplication
get() = applicationContext as YourApplication

val Context.yourService: YourService
get() = application.yourService

// 服务层,用于管理链接和处理数据
class YourService {

/**
* 一个简单的没有任何附加逻辑的 WebSocket 连接 Handler ,只是把消息缓存到一个 Flow 中
* 你可以加上你自己的逻辑,比如消息解析,消息处理等,链接重试之类的
*/
class ServiceConnection {

internal var websocket: WebSocket? = null
private val messageBuffer = MutableSharedFlow<String>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val receivedMessages = messageBuffer.asSharedFlow()

fun onConnected(websocket: WebSocket) {
// ...
}

fun onMessageReceived(message: String) {
// ...
}

fun disconnect(code: Int, reason: String, cause: Throwable? = null) {
// ...
}

fun sendMessage(message: String) {
// ...
}

}

private val client = OkHttpClient()

// 一个简单的 WebSocket 连接缓存,只保留一个连接
private var activeConnection: ServiceConnection? = null

@Synchronized
fun connect(): Result<ServiceConnection> {
if (activeConnection != null) {
Result.success(activeConnection!!)
}
return connectChecked().onSuccess {
activeConnection = it
}
}

private fun connectChecked(): Result<ServiceConnection> {
val request = Request.Builder()
.url("wss://echo.websocket.org")
.build()
val connection = ServiceConnection()
return kotlin.runCatching {
client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
connection.onConnected(webSocket)
}

override fun onMessage(webSocket: WebSocket, text: String) {
connection.onMessageReceived(text)
}

override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
connection.disconnect(code, reason)
}

override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
connection.disconnect(0, t.message ?: "Unknown error", t)
}
})
.apply {
connection.websocket = this
}
connection
}
}

}

// 处理 UI 状态、用户事件,和与服务层拉取数据
class YourViewModel(
application: Application,
) : AndroidViewModel(application) {

private val yourService: YourService
get() = getApplication<Application>().yourService

private var connection: YourService.ServiceConnection? = null

fun connect() {
viewModelScope.launch {
connection = yourService.connect()
.onSuccess {
it.receivedMessages.collect(::onReceiveMessage)
}.onFailure {
// handle error

}.getOrNull()
}
}

private fun onReceiveMessage(
message: String
) {
// update ui state
}

}

class YourUi: Fragment() {

private val viewModel: YourViewModel by viewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}

override fun onStart() {
super.onStart()
viewModel.connect() // connect to service
}

}
我现在用 Compose 和 Navigation 写应用,现在好多地方需要复制 Compose 官方的代码,比如对话框和上下文菜单,甚至 Navigation 也要复制修改。
@yuhuazhu 据说车载还在用 Java
@debuggeeker 老项目当然不动,总会写与历史代码关联不大的新功能,然后你就会面临是继续用历史代码搞,还是推倒重来。
@eyeshuaji 能装第三方应用么,文件管理器之类的,看下路径,电视厂商可能改了东西,有可能是放到了 USB 上。我之前维护过一段时间的 wps 投影宝,主要支持小米电视。
应该还挂载/sdcard 吧,一般来说 emulated 表示存储空间是经过加密的,区分了多用户。老设备先看看/sdcard 一般等同于/storage/emulated/0
可能有个加载框,加载框每消失就不能打开菜单。
这个还不严重,严重的是调试器在调试第三方库的 supend 调用是无法 step over 的,会卡住。
1  2  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2775 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 27ms · UTC 14:50 · PVG 22:50 · LAX 06:50 · JFK 09:50
Developed with CodeLauncher
♥ Do have faith in what you're doing.