首页
工具
隐私协议
App Privacy Policy
更多
作品
关于我们
Search
1
android5遇到INSTALL_FAILED_DEXOPT 解决办法
1,664 阅读
2
设置max_connections无效
1,484 阅读
3
FlexboxLayout+recyclerView实现自动换行
1,374 阅读
4
Nginx配置多个域名
1,257 阅读
5
Android P http网络请求失败
1,230 阅读
默认分类
mysql
android
android深入
Jetpack Compose
Android传感器
php
Yii2
windows
webrtc
登录
Search
标签搜索
android
kotlin
webrtc
kurento
mysql
adb
nginx
flutter
rsa
微信
git
Yii2
md5
加密
dart
aes
wechat
windows
小程序
dexopt
Typecho
累计撰写
80
篇文章
累计收到
3
条评论
首页
栏目
默认分类
mysql
android
android深入
Jetpack Compose
Android传感器
php
Yii2
windows
webrtc
页面
工具
隐私协议
App Privacy Policy
作品
关于我们
搜索到
52
篇与
android
的结果
2022-11-07
Android Webrtc之Kurento-one2one-call
webrtc对等连接ClientA注册,ClientB注册.ClientA创建SdpOffer,成功后调用setLocalDescription()设置为本地描述.ClientA发起Call,将SdpOffer发送给信令服务器.ClientB接收到请求(incomingCall),ClientB接受后配置本地媒体数据.ClientB创建SdpOffer,成功后调用setLocalDescription()设置为本地描述.ClientB发起incomingCallResponse,将sdpOffer发送给信令服务器.ClientB接收到startCommunication,调用setRemoteDescription将SdpAnswer设置为远程描述,ClientB已经获知连接双方的配置.ClientA接收到callResponse,调用setRemoteDescription将SdpAnswer设置为远程描述,ClientA已经获知连接双方的配置.信令主要流程图引入库 implementation 'org.webrtc:google-webrtc:1.0.32006' implementation 'org.java-websocket:Java-WebSocket:1.5.3' implementation "com.google.code.gson:gson:2.+"初始化PeerConnectionFactoryPeerConnectionFactory.initialize( PeerConnectionFactory.InitializationOptions.builder( this.applicationContext ) .setFieldTrials("WebRTC-H264HighProfile/Enabled/") .setEnableInternalTracer(true) .createInitializationOptions() )创建PeerConnecitonFactoryval encoderFactory = DefaultVideoEncoderFactory(bglBase.eglBaseContext, true, true) val decoderFactory = DefaultVideoDecoderFactory(bglBase.eglBaseContext) peerConnectionFactory = PeerConnectionFactory.builder() .setVideoEncoderFactory(encoderFactory) .setVideoDecoderFactory(decoderFactory) .createPeerConnectionFactory()创建PeerConnection val peerConnectionFactory = createPeerConnectionFactory() // 配置STUN穿透服务器 转发服务器 val iceServers = ArrayList<PeerConnection.IceServer>() val iceServer: PeerConnection.IceServer = PeerConnection.IceServer.builder(Config.STUN).createIceServer() iceServers.add(iceServer) // streamList = ArrayList() val configuration = PeerConnection.RTCConfiguration(iceServers) peerConnection = peerConnectionFactory.createPeerConnection(configuration, this) }在Observer.onIceCandidate响应时,将信息发送给服务端override fun onIceCandidate(iceCandidate: IceCandidate?) { //发送IceCandidate sendIceCandidate(iceCandidate) }在OnMessage接收到iceCandidate添加到peerConnectionpeerConnection.addIceCandidate(iceCandidate)初始化WebSocketClientval webSocketClient=object :WebSocketClient(URI.create(Config.URL)){ override fun onOpen(handshakedata: ServerHandshake?) { Log.i(TAG, "WebSocket连接成功") } override fun onMessage(message: String?) { Log.e( TAG, "######## onMessage ########\n$message" ) val jsonObject = Gson().fromJson(message, JsonObject::class.java) when (jsonObject["id"].asString) { REGISTER_RESPONSE -> { // 注册回应 } INCOMING_CALL -> { //来电 } CALL_RESPONSE -> { //呼叫回应 } START_COMMUNICATION -> { //开始通迅 } STOP_COMMUNICATION->{ //停止通迅 } ICE_CANDIDATE -> { //接收到IceCandidate后调用addIceCandidate } } } override fun onClose(code: Int, reason: String?, remote: Boolean) { Log.i(TAG, "onClose code=$code reason=$reason remote=$remote") } override fun onError(ex: Exception?) { ex?.printStackTrace() } }通过PeerConnectionFactory创建数据源VideoTrack/AudioTrack准备摄像头,是否有前置摄像头,如果有就用前置摄像头:private fun createCameraCapturer(enumerator: CameraEnumerator): VideoCapturer? { val deviceNames = enumerator.deviceNames // First, try to find front facing camera Log.d(TAG, "Looking for front facing cameras.") for (deviceName in deviceNames) { if (enumerator.isFrontFacing(deviceName)) { Logging.d(TAG, "Creating front facing camera capturer.") val videoCapturer: VideoCapturer? = enumerator.createCapturer(deviceName, null) if (videoCapturer != null) { return videoCapturer } } } // Front facing camera not found, try something else Log.d(TAG, "Looking for other cameras.") for (deviceName in deviceNames) { if (!enumerator.isFrontFacing(deviceName)) { Logging.d(TAG, "Creating other camera capturer.") val videoCapturer: VideoCapturer? = enumerator.createCapturer(deviceName, null) if (videoCapturer != null) { return videoCapturer } } } return null }在Android系统下有两种Camera,一种称为 Camera1,是一种比较老的采集视频数据的方式,别一种称为Camera2,是一种新的采集视频的方法。它们之间的最大区别是Camera1使用同步方式调用API,Camera2使用异步方式,所以Camera2更高效。这里如果支持Camera2就使用Camera2, 如果不支持就使用Camera1。 private fun createVideoCapturer(): VideoCapturer { return if (Camera2Enumerator.isSupported(this)) { createCameraCapturer(Camera2Enumerator(this))!! } else { createCameraCapturer(Camera1Enumerator(true))!! } }配置视频数据源VideoTrack val videoSource = peerConnectionFactory.createVideoSource(true) val surfaceTextureHelper = SurfaceTextureHelper.create( Thread.currentThread().name, eglBase().eglBaseContext ) val videoCapturer: VideoCapturer = createVideoCapturer() //将videoCapturer与videoSource绑定在一起 videoCapturer.initialize( surfaceTextureHelper, this.applicationContext, videoSource.capturerObserver ) //调用startCapture打开摄像头 videoCapturer.startCapture( Config.VIDEO_RESOLUTION_WIDTH, Config.VIDEO_RESOLUTION_HEIGHT, Config.VIDEO_FPS ) val videoTrack = peerConnectionFactory.createVideoTrack(Config.VIDEO_TRACK_ID, videoSource) videoTrack.setEnabled(true) videoTrack.addSink(localSurfaceView) 配置音频数据源AudioTrack val audioConstraints = MediaConstraints() //回声消除 audioConstraints.mandatory.add( MediaConstraints.KeyValuePair( "googEchoCancellation", "true" ) ) //自动增益 audioConstraints.mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl", "true")) //高音过滤 audioConstraints.mandatory.add(MediaConstraints.KeyValuePair("googHighpassFilter", "true")) //噪音处理 audioConstraints.mandatory.add( MediaConstraints.KeyValuePair( "googNoiseSuppression", "true" ) ) val audioSource = peerConnectionFactory.createAudioSource(audioConstraints) val audioTrack = peerConnectionFactory.createAudioTrack(Config.AUDIO_TRACK_ID, audioSource) 添加音视频到MediaStreamval medisStream = peerConnectionFactory.createLocalMediaStream("local_stream") medisStream.addTrack(audioTrack) medisStream.addTrack(videoTrack)同样在onAddTrack添加远程视频流即可,以下是两个手机的测试:
2022年11月07日
174 阅读
0 评论
0 点赞
2022-11-06
搭建Kurento服务
在Centos7上搭建Kurento官方网址:https://github.com/Kurento/kurento-media-server官网教程:https://doc-kurento.readthedocs.io/en/latest/user/installation.htmlKurento是一个WebRTC媒体服务器,同时提供了一系列的客户端API,可以简化供浏览器、移动平台使用的视频类应用程序的开发。Kurento支持:群组通信(group communications)媒体流的转码(transcoding)、录制(recording)、广播(broadcasting)、路由(routing)高级媒体处理特性,包括:机器视觉(CV)、视频索引、增强现实(AR)、语音分析Kurento的模块化架构使其与第三方媒体处理算法 —— 语音识别、人脸识别 —— 很容易集成。和大部分多媒体通信技术一样,Kurento应用的整体架构包含两个层(layer)或者叫平面(plane)信号平面(Signaling Plane):负责通信的管理,例如媒体协商、QoS、呼叫建立、身份验证等媒体平面(Media Plane):负责媒体传输、编解码等WebRTC让浏览器能够进行实时的点对点通信(在没有服务器的情况下)。但是要想实现群组通信、媒体流录制、媒体广播、转码等高级特性,没有媒体服务器是很难实现的。Kurento的核心是一个媒体服务器(Kurento Media Server,KMS),负责媒体的传输、处理、加载、录制,主要基于 GStreamer实现。此媒体服务器的特性包括:网络流协议处理,包括HTTP、RTP、WebRTC支持媒体混合(mixing)、路由和分发的群组通信(MCU、SFU功能)对机器视觉和增强现实过滤器的一般性支持媒体存储支持,支持对WebM、MP4进行录像操作,可以播放任何GStreamer支持的视频格式对于GStreamer支持的编码格式,可以进行任意的转码,例如VP8, H.264, H.263, AMR, OPUS, Speex, G.711安装coturnGithub:https://github.com/coturn/coturnyum install -y openssl-devel libevent-devel git #clone 源码 git clone https://github.com/coturn/coturn.git cd coturn ./configure make sudo make install # 启动turnserver nohup turnserver -L 0.0.0.0 -a -u kurento:kurento -v -f -r psvmc.cn & #然后查看相应的端口号3478是否存在进程 sudo lsof -i:3478这样就说明已经可以启动了,接下来我们先停掉turnserver,重新配置turnserver 默认加载配置文件/usr/local/etc/turnserver.conf.cp /usr/local/etc/turnserver.conf.default /usr/local/etc/turnserver.conf #生成证书 openssl req -x509 -newkey rsa:2048 -keyout /usr/local/etc/turn_server_pkey.pem -out /usr/local/etc/turn_server_cert.pem -days 99999 -nodes修改turnserver.conf:vi /usr/local/etc/turnserver.conf内容:# 设置转发的ip(局域网ip),如果不设置,他会自己选择默认的 relay-ip=****** # 转发的外网ip(本机外网ip),用于NAT 地址映射 external-ip=****** # 转发的线程数,其实默认不设置最好 relay-threads=5 #UDP 最小端口和最大端口 min-port=40000 max-port=60000 # WebRTC 的消息里会用到 fingerprint # WebRTC 认证需要 lt-cred-mech #中继服务器的监听器IP地址 listening-ip=0.0.0.0 #静态账号 user=kurento:kurento # 统计状态信息的redis db # redis-statsdb="ip=xx.xx.xxx.xx dbname=3 password=xxxx port=6379 connect_timeout=30" # 用户登录域(例如:dev.com) realm=**** # 证书 cert=/usr/local/etc/turn_server_cert.pem pkey=/usr/local/etc/turn_server_pkey.pem # 输出log log-file=stdout mobility重新启动turnserver:nohup turnserver -c /usr/local/etc/turnserver.conf -v | ts '[%Y-%m-%d %H:%M:%S]' >> /usr/local/etc/turn.log 2>&1 &注意:这个ts命令,是openssl的一个指令,用来打印日期yum install moreutils man ts echo -e "hello world" | ts '[%Y-%m-%d %H:%M:%S]'打开服务器端口:firewall-cmd --zone=public --add-port=3478/tcp --permanent firewall-cmd --zone=public --add-port=3478/udp --permanent firewall-cmd --reload测试stunhttps://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/remove掉默认的stunstun:stun.l.google.com:19302测试stun不需要账号密码,添加上stun:YOUR__IP:3478,然后点击Gather candidates只要出现Done即为成功,下面提示的fail不用在意测试turn添加turn:YOUR__IP:3478,username/password填配置文件里的kurento/kurento同样只要出现Done即为成功用docker安装kurentodocker pull kurento/kurento-media-server:latest运行docker run -d --name kms -p:8888:8888/tcp kurento/kurento-media-server:latest查看一下docker ps -aede4f883c11d kurento/kurento-media-server:latest “/entrypoint.sh” 5 seconds ago Up 4 seconds (health: starting) 0.0.0.0:8888->8888/tcp进入Dockersudo docker exec -it 704d3ed68a49 bash测试服务是否正常curl \ --include \ --header "Connection: Upgrade" \ --header "Upgrade: websocket" \ --header "Host: 127.0.0.1:8888" \ --header "Origin: 127.0.0.1" \ http://127.0.0.1:8888/kurento正常启动会显示{card-describe title="提示"}HTTP/1.1 500 Internal Server ErrorServer: WebSocket++/0.7.0{/card-describe}退出容器exit该Kurento媒体服务器的端口(KMS)过程中默认监听8888客户端WebSocket连接ws://YOUR_IP:8888/kurento配置kurento服务器进入kurento的镜像编辑kurento的配置文件:#进入镜像 docker exec -it kms /bin/bash #安装vim apt-get update apt-get install vim #进入配置文件夹 cd /etc/kurento/modules/kurento/ #编辑配置文件 vim WebRtcEndpoint.conf.ini修改stun和turn信息stunServerAddress=YOUR_IP stunServerPort=3478 turnURL=kurento:kurento@YOUR_IP:3478?transport=udp退出容器,重启kurentoexit docker restart kms测试git clone https://gitee.com/psvmc/kurento-tutorial-java.git cd kurento-tutorial-java/kurento-hello-world vi src/main/resources/static/js/index.js在函数function uiStart()里,增加一个叫iceservers的变量,格式如下:let iceservers={ "iceServers":[ { urls:"stun:YOUR_IP:3478" }, { urls:["turn:YOUR_IP:3478"], username:"kurento", credential: "kurento" } ] }修改option:const options = { localVideo: uiLocalVideo, remoteVideo: uiRemoteVideo, mediaConstraints: { audio: true, video: true }, onicecandidate: (candidate) => sendMessage({ id: 'ADD_ICE_CANDIDATE', candidate: candidate, }), configuration: iceservers //这里增加了一个configuration的key };启动项目mvn -U clean spring-boot:run -Dkms.url=ws://your_ip:8888/kurento启动完之后用浏览器打开demo页面https://your_ip:8443/
2022年11月06日
149 阅读
0 评论
0 点赞
2022-10-18
Android传感器说明
名称说明TYPE_ACCELEROMETER加速度传感器TYPE_MAGNETIC_FIELD磁场传感器TYPE_GYROSCOPE陀螺仪传感器TYPE_LIGHT光照传感器TYPE_PRESSURE气压传感器TYPE_TEMPERATURE手机内部温度传感器TYPE_PROXIMITY距离传感器TYPE_GRAVITY重力传感器TYPE_LINEAR_ACCELERATION线性加速度传感器TYPE_ROTATION_VECTOR旋转矢量传感器TYPE_RELATIVE_HUMIDITY湿度传感器TYPE_AMBIENT_TEMPERATURE手机外部温度传感器TYPE_STEP_DETECTOR累计步数传感器TYPE_STEP_COUNTER单次步数传感器TYPE_ORIENTATION方向传感器检查传感器服务//获取传感器服务 sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager //获取当前手机支持的感应器并打印 sensorManager.getSensorList(Sensor.TYPE_ALL).forEach { Log.e(TGA, "Sensor:${it.name} ${it.stringType}") }
2022年10月18日
185 阅读
0 评论
0 点赞
2021-09-30
Jetpack Compose 底部导航栏
注意:您需要有Android Studio Arctic Fox 及更高版本才能在您的项目中使用Jetpack Compose。添加库转到您的项目级gradle.build文件,并添加以下扩展名:buildscript { ext { compose_version = '1.0.2' } // ... }现在转到应用级gradle.build文件,并添加以下内容:android { // ... kotlinOptions { jvmTarget = '1.8' useIR = true } buildFeatures { // ... compose true } composeOptions { kotlinCompilerExtensionVersion compose_version } } dependencies { // ... implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling:$compose_version" implementation "androidx.navigation:navigation-compose:2.4.0-alpha08" implementation "androidx.activity:activity-compose:1.3.1" // ... }创建顶部栏打开activity(例如MainActivity.kt),并在类之外添加以下可组合函数以创建topbar@Composable fun TopBar() { TopAppBar( title = { Text(text = stringResource(R.string.app_name), fontSize = 18.sp) }, backgroundColor = colorResource(id = R.color.colorPrimary), contentColor = Color.White ) } @Preview(showBackground = true) @Composable fun TopBarPreview() { TopBar() }创建底部导航栏在创建底部导航栏之前,我们必须准备Item;创建一个Sealed Class并指定NavigationItem作为名称。并使用参数为每个条形项目创建一个模型:route:必须是unique。用来从底部导航栏导航到视图icon : 栏项的图标title : 栏项的名称sealed class NavigationItem(var route: String, var icon: Int, var title: String) { object Home : NavigationItem("home", R.mipmap.ic_home, "首页") object Recommend : NavigationItem("recommend", R.mipmap.ic_recommend, "关注") object Books : NavigationItem("books", R.mipmap.ic_book, "书城") object Profile : NavigationItem("profile", R.mipmap.ic_profile, "我的") }同样,在MainActivity.kt 中,添加以下可组合函数:@Composable fun BottomNavigationBar() { val items = listOf( NavigationItem.Home, NavigationItem.Recommend, NavigationItem.Books, NavigationItem.Profile ) BottomNavigation( backgroundColor = colorResource(id = R.color.colorPrimary), contentColor = Color.White ) { items.forEach { item -> BottomNavigationItem( icon = { Icon(painterResource(id = item.icon), contentDescription = item.title) }, label = { Text(text = item.title) }, selectedContentColor = Color.White, unselectedContentColor = Color.White.copy(0.4f), alwaysShowLabel = true, selected = false, onClick = { /* Add code later */ } ) } } } @Preview(showBackground = true) @Composable fun BottomNavigationBarPreview() { BottomNavigationBar() }创建包含顶部栏和底部导航栏的视图这里我将使用Scaffold,创建一个名为MainScreen()的新组合函数,里面有一个Scaffold布局,并添加我们之前创建的TopBar()和BottomNavigationBar()class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MainScreen() } } } @Composable fun MainScreen() { Scaffold( topBar = { TopBar() }, bottomBar = { BottomNavigationBar() } ) { /* Add code later */ } } @Preview(showBackground = true) @Composable fun MainScreenPreview() { MainScreen() }将导航栏与视图连接起来(导航)连接之前,需要先创建Views,我创建了4个名为*Screen的 Kotlin 文件HomeScreen.kt@Composable fun HomeScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Home View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun HomeScreenPreview() { HomeScreen() }RecommendScreen.kt@Composable fun RecommendScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Recommend View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun RecommendScreenPreview() { RecommendScreen() }BooksScreen.kt@Composable fun BooksScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Books View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun BooksScreenPreview() { BooksScreen() }ProfileScreen.kt@Composable fun ProfileScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Profile View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun ProfileScreenPreview() { ProfileScreen() }接下来在BottomNavigationBar中,添加参数navController,并创建navBackStackEntry和currentRoute。使用currentRoute,我们检查是否必须突出显示栏项目,然后使用navigate()方法导航到视图@Composable fun BottomNavigationBar(navController: NavController) { val items = listOf( NavigationItem.Home, NavigationItem.Recommend, NavigationItem.Books, NavigationItem.Profile ) BottomNavigation( backgroundColor = colorResource(id = R.color.colorPrimary), contentColor = Color.White ) { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route items.forEach { item -> BottomNavigationItem( icon = { Icon(painterResource(id = item.icon), contentDescription = item.title) }, label = { Text(text = item.title) }, selectedContentColor = Color.White, unselectedContentColor = Color.White.copy(0.4f), alwaysShowLabel = true, selected = currentRoute == item.route, onClick = { navController.navigate(item.route) { navController.graph.startDestinationRoute?.let { route -> popUpTo(route) { saveState = true } } launchSingleTop = true // 重新选择同一项目时避免同一目的地的多个副本 restoreState = true } } ) } } } @Preview(showBackground = true) @Composable fun BottomNavigationBarPreview() { // BottomNavigationBar() }创建一个新的可组合函数并将其命名为Navigation(),参数为NavHostController类型的参数navController。在这里,我们创建了一个NavHost,我们将Home视图设置为startDestination,我们将每个View的路由设置为可组合的。@Composable fun Navigation(navController: NavHostController) { NavHost(navController, startDestination = NavigationItem.Home.route) { composable(NavigationItem.Home.route) { HomeScreen() } composable(NavigationItem.Recommend.route) { RecommendScreen() } composable(NavigationItem.Books.route) { BooksScreen() } composable(NavigationItem.Profile.route) { ProfileScreen() } } }在MainScreen()中创建NavigationController,并将其传递给BottomNavigationBar和Navigation@Composable fun MainScreen() { val navController = rememberNavController() Scaffold( topBar = { TopBar() }, bottomBar = { BottomNavigationBar(navController) } ) { Navigation(navController) } }
2021年09月30日
600 阅读
0 评论
0 点赞
2021-07-27
adb shell input keyevent
通过adb shell input keyevent,event_code发送到设备usage: input [text|keyevent] input text <string> input keyevent <event_code>event_code:0 --> "KEYCODE_UNKNOWN" 1 --> "KEYCODE_MENU" 2 --> "KEYCODE_SOFT_RIGHT" 3 --> "KEYCODE_HOME" 4 --> "KEYCODE_BACK" 5 --> "KEYCODE_CALL" 6 --> "KEYCODE_ENDCALL" 7 --> "KEYCODE_0" 8 --> "KEYCODE_1" 9 --> "KEYCODE_2" 10 --> "KEYCODE_3" 11 --> "KEYCODE_4" 12 --> "KEYCODE_5" 13 --> "KEYCODE_6" 14 --> "KEYCODE_7" 15 --> "KEYCODE_8" 16 --> "KEYCODE_9" 17 --> "KEYCODE_STAR" 18 --> "KEYCODE_POUND" 19 --> "KEYCODE_DPAD_UP" 20 --> "KEYCODE_DPAD_DOWN" 21 --> "KEYCODE_DPAD_LEFT" 22 --> "KEYCODE_DPAD_RIGHT" 23 --> "KEYCODE_DPAD_CENTER" 24 --> "KEYCODE_VOLUME_UP" 25 --> "KEYCODE_VOLUME_DOWN" 26 --> "KEYCODE_POWER" 27 --> "KEYCODE_CAMERA" 28 --> "KEYCODE_CLEAR" 29 --> "KEYCODE_A" 30 --> "KEYCODE_B" 31 --> "KEYCODE_C" 32 --> "KEYCODE_D" 33 --> "KEYCODE_E" 34 --> "KEYCODE_F" 35 --> "KEYCODE_G" 36 --> "KEYCODE_H" 37 --> "KEYCODE_I" 38 --> "KEYCODE_J" 39 --> "KEYCODE_K" 40 --> "KEYCODE_L" 41 --> "KEYCODE_M" 42 --> "KEYCODE_N" 43 --> "KEYCODE_O" 44 --> "KEYCODE_P" 45 --> "KEYCODE_Q" 46 --> "KEYCODE_R" 47 --> "KEYCODE_S" 48 --> "KEYCODE_T" 49 --> "KEYCODE_U" 50 --> "KEYCODE_V" 51 --> "KEYCODE_W" 52 --> "KEYCODE_X" 53 --> "KEYCODE_Y" 54 --> "KEYCODE_Z" 55 --> "KEYCODE_COMMA" 56 --> "KEYCODE_PERIOD" 57 --> "KEYCODE_ALT_LEFT" 58 --> "KEYCODE_ALT_RIGHT" 59 --> "KEYCODE_SHIFT_LEFT" 60 --> "KEYCODE_SHIFT_RIGHT" 61 --> "KEYCODE_TAB" 62 --> "KEYCODE_SPACE" 63 --> "KEYCODE_SYM" 64 --> "KEYCODE_EXPLORER" 65 --> "KEYCODE_ENVELOPE" 66 --> "KEYCODE_ENTER" 67 --> "KEYCODE_DEL" 68 --> "KEYCODE_GRAVE" 69 --> "KEYCODE_MINUS" 70 --> "KEYCODE_EQUALS" 71 --> "KEYCODE_LEFT_BRACKET" 72 --> "KEYCODE_RIGHT_BRACKET" 73 --> "KEYCODE_BACKSLASH" 74 --> "KEYCODE_SEMICOLON" 75 --> "KEYCODE_APOSTROPHE" 76 --> "KEYCODE_SLASH" 77 --> "KEYCODE_AT" 78 --> "KEYCODE_NUM" 79 --> "KEYCODE_HEADSETHOOK" 80 --> "KEYCODE_FOCUS" 81 --> "KEYCODE_PLUS" 82 --> "KEYCODE_MENU" 83 --> "KEYCODE_NOTIFICATION" 84 --> "KEYCODE_SEARCH" 85 --> "TAG_LAST_KEYCODE"
2021年07月27日
443 阅读
0 评论
0 点赞
2021-07-23
Android Studio查找中文
打开搜索框^((?!(\*|//)).)+[\u4e00-\u9fa5]
2021年07月23日
559 阅读
0 评论
0 点赞
2021-07-10
kotlin-android-extensions 已被弃用,如何使用 @Parcelize?
第 1 步。更新到最新的 kotlin 版本 -1.4.20并替换apply plugin: 'kotlin-android-extensions'toapply plugin: 'kotlin-parcelize'或者plugins { .. id 'kotlin-parcelize' }第 2 步。从 android {} 中删除以下代码androidExtensions { experimental = true }第 3 步。最后,替换旧的 import ->import kotlinx.android.parcel.Parcelizetoimport kotlinx.parcelize.Parcelize新插件:https : //plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.parcelize
2021年07月10日
517 阅读
0 评论
0 点赞
2021-07-09
迁移已弃用的 Kotlin Android Extension 使用ViewBinding
最近出现在了一个这样的警告:The 'kotlin-android-extensions' Gradle plugin is deprecated. Please use this migration guide (https://goo.gle/kotlin-android-extensions-deprecation) to start working with View Binding (https://developer.android.com/topic/libraries/view-binding) and the 'kotlin-parcelize' plugin.kotlinx.android.synthetic 不再是推荐的做法,删除支持显式findViewById,用ViewBinding替代ViewBinding与 Kotlin Extensions相比,它增加了视图查找和类型安全的编译时检查。但视图绑定后,可让您更轻松地编写与视图交互的代码。启用视图绑定后,它会为该模块中存在的每个 XML 布局文件生成一个绑定类。如何启用视图ViewBinding?在build.gradle中添加:android { .. buildFeatures { viewBinding true } }如何使用ViewBinding?如果为模块启用了视图绑定,则会为模块包含的每个 XML 布局文件生成一个绑定类。每个绑定类都包含对根视图和所有具有 ID 的视图的引用。绑定类的名称是通过将 XML 文件的名称转换为 Pascal 大小写并Binding在末尾添加单词来生成的。在Activity中使用ViewBindingprivate lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) }使用binding对象访问Viewbinding.name.text = "this is ViewBinding"在Fragment中使用ViewBinding在Fragment中使用ViewBinding需要注意,因为ViewBinding不能很好地与Fragment一起使用,如果不在OnDestroy清除,则它不会从内存中清除,导致内存泄漏。private var _binding: FragmentMainBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentMainBinding.inflate(inflater, container, false) return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null }像在activity中一样使用对象访问视图binding.name.text = "this is ViewBinding"备注ViewBinding将为模块中的每个XML布局生成一个绑定对象,例如:activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>ViewBinding将生成 ActivityMainBinding.javapublic final class ActivityMainBinding implements ViewBinding { @NonNull private final ConstraintLayout rootView; @NonNull public final TextView textView; ... }ViewBinding将为每个具有指定id. 在ActivityMainBinding.java中,ViewBinding生成一个公共inflate方法。它调用bind将绑定属性的位置,并进行一些错误检查。
2021年07月09日
763 阅读
0 评论
0 点赞
2021-04-02
Room数据库Schema export Error
在使用Android Room数据库的时候报错了:Schema export directory is not provided to the annotation processor so we cannot export the schema. You can either provide `room.schemaLocation` annotation processor argument OR set exportSchema to false.如果您不需要检查架构并且想要摆脱警告,只需将exportSchema = false添加到RoomDatabase中,如下所示:@Database(entities = { YourEntity.class }, version = 1, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { //... }另一种方法是在应用模块的build.gradle文件中,将此文件添加到defaultConfig部分(在android部分下面)。这会将架构写到项目文件夹的schemas子文件夹中。android { // ... (compileSdkVersion, buildToolsVersion, etc) defaultConfig { // ... (applicationId, miSdkVersion, etc) kapt { arguments { arg("room.schemaLocation","$projectDir/schemas".toString()) } } } buildTypes { // ... (buildTypes, compileOptions, etc) } } //...以上是Kotlin,如果是Java:android { // ... (compileSdkVersion, buildToolsVersion, etc) defaultConfig { // ... (applicationId, miSdkVersion, etc) javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation":"$projectDir/schemas".toString()] } } } // ... (buildTypes, compileOptions, etc) }当执行项目后,在Android Studio 的Project视图下,查看项目,会发现Module生成了一个schemas的文件夹,打开.json文件看起来像这样:{ "formatVersion": 1, "database": { "version": 1, "identityHash":"6240057b6178b803a0bf9915edf969e3", "entities": [ { "tableName":"sms_table", "createSql":"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` TEXT, `date` INTEGER, `client_id` INTEGER)", "fields": [ { "fieldPath":"id", "columnName":"id", "affinity":"INTEGER" }, { "fieldPath":"message", "columnName":"message", "affinity":"TEXT" }, { "fieldPath":"date", "columnName":"date", "affinity":"INTEGER" }, { "fieldPath":"clientId", "columnName":"client_id", "affinity":"INTEGER" } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [], "foreignKeys": [] } ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, "6240057b6178b803a0bf9915edf969e3")" ] } }
2021年04月02日
848 阅读
0 评论
0 点赞
2021-03-29
Android改变状态栏文本颜色(WHITE/BLACK)
API> = 23从API v23及更高版本开始,可以将以下内容添加到AppTheme styles.xml中:解决方案1// View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR: 将状态栏的文字设置为黑色 window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR解决方案1<item name="android:statusBarColor">@color/colorPrimaryDark</item> <item name="android:windowLightStatusBar">true</item>当android:windowLightStatusBar设置为true,状态栏文字颜色将能够可以看出,当状态栏的颜色是白色的,而当android:windowLightStatusBar设置为false,状态栏文字颜色将被设计在状态栏的颜色是可以看到暗。API <23在Android中无法更改状态栏的颜色。可以在应用程序中设置状态栏的背景色val window: Window = activity.getWindow() // clear FLAG_TRANSLUCENT_STATUS flag: window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) // add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) // finally change the color window.setStatusBarColor(ContextCompat.getColor(activity,R.color.my_statusbar_color))https://developer.android.google.cn/reference/android/view/Window.html#setStatusBarColor(int)
2021年03月29日
257 阅读
0 评论
0 点赞
1
2
3
...
6