cameraX视频录制 拷贝直接用

2023-05-16

文章目录

  • 效果图
  • activity代码
  • 项目地址.

最下面是GIT 地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3i0EaImv-1637722081187)(https://liudao01.github.io/picture/img/视频录制.gif)]

https://liudao01.github.io/picture/img/视频录制.gif

效果图

在这里插入图片描述

activity代码

package com.sinochem.www.station.activity

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.app.ProgressDialog
import android.content.pm.PackageManager
import android.graphics.Point
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.os.SystemClock
import android.text.TextUtils
import android.util.Log
import android.util.Size
import android.view.View
import android.widget.*
import androidx.annotation.NonNull
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.common.util.concurrent.ListenableFuture
import com.sinochem.www.station.R
import com.sinochem.www.station.base.BaseActivity
import com.sinochem.www.station.utils.*
import com.sinochem.www.station.utils.camerax.CameraXCustomPreviewView
import com.sinochem.www.station.utils.camerax.FocusImageView
import com.sinochem.www.station.view.LoadingFragment
import java.io.File
import java.lang.ref.WeakReference
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

// Typealias (本文下称 "类型别名")。类型别名可以使您在不增加新类型的情况下,为现有类或函数类型提供替代名称。
typealias LumaListener = (luma: Double) -> Unit

class CameraXActivity : BaseActivity(), View.OnClickListener {
    private val title: RelativeLayout by lazy { findViewById<RelativeLayout>(R.id.title) }
    private val viewFinder: CameraXCustomPreviewView by lazy {
        findViewById<CameraXCustomPreviewView>(
            R.id.viewFinder
        )
    }
    private val back: ImageView by lazy { findViewById<ImageView>(R.id.back) }

    private val switchBtn: Button by lazy { findViewById<Button>(R.id.switch_btn) }
    private val chronometer: Chronometer by lazy { findViewById<Chronometer>(R.id.chronometer) }
    private val recorderStart: ImageView by lazy { findViewById<ImageView>(R.id.recorder_start) }
    private val recorderStop: ImageView by lazy { findViewById<ImageView>(R.id.recorder_stop) }
    private val focusView: FocusImageView by lazy { findViewById<FocusImageView>(R.id.focusView) }


    private lateinit var cameraExecutor: ExecutorService

    var cameraProvider: ProcessCameraProvider? = null//相机信息
    var preview: Preview? = null//预览对象
    var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA//当前相机
    var camera: Camera? = null//相机对象
    private var imageCapture: ImageCapture? = null//拍照用例
    var videoCapture: VideoCapture? = null//录像用例

    var maxMinuie = 180 //最大时间

    val minMinuie = 5 //最小时间
    private var progressDialog: ProgressDialog? = null

    var localPath: String? = null;

    var isTimeOver: Boolean = false //是否时间结束

    var alertDialogBuilder: AlertDialog.Builder? = null

    override fun setLayoutId(): Int {
        return R.layout.activity_camerax_record;
    }

    override fun initVariables() {
        if (intent != null) {
            maxMinuie = intent.getIntExtra("maxMinuie", 180)
        }
        LogUtil.d("最大录制时长 :  $maxMinuie")

        // 设置视频文件输出的路径
        // 设置视频文件输出的路径
        localPath = (PathUtil.getInstance().videoPath
                + System.currentTimeMillis() + ".mp4")
    }

    override fun initViews(savedInstanceState: Bundle?) {
    }

    override fun doBusiness() {
        recorderStop.setOnClickListener(this)
        recorderStart.setOnClickListener(this)
        back.setOnClickListener(this)

        chronometer.onChronometerTickListener =
            Chronometer.OnChronometerTickListener { chronometer ->
                val recordingTime = SystemClock.elapsedRealtime() - chronometer.base // 保存这次记录了的时间
                val contentDescription = chronometer.contentDescription
                val second = Math.round(recordingTime.toFloat() / 1000).toFloat()
                LogUtil.d("打印回调计时器 = " + second)
                if (maxMinuie <= second) {
                    isTimeOver = true
                    recorderStopAction();
                }
            }
    }


    @SuppressLint("ClickableViewAccessibility")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContentView()

        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }


//        camera_capture_button.setOnClickListener { takePhoto() }
//        recorderStart.setOnClickListener {
            btnStartVideo.text = "Stop Video"
//            takeVideo()
//        }
        switchBtn.setOnClickListener {
            if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
                cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
            } else {
                cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            }
            startCamera()
        }
    }

    /**
     * 初始化手势动作
     */
    private fun initCameraListener() {
        val zoomState = camera!!.cameraInfo.zoomState
        viewFinder.setCustomTouchListener(object : CameraXCustomPreviewView.CustomTouchListener {
            override fun zoom(delta: Float) {
                //双指缩放
//                zoomState.value?.let {
//                    val currentZoomRatio = it.zoomRatio
//                    camera!!.cameraControl.setZoomRatio(currentZoomRatio * delta)
//                }
            }

            override fun click(x: Float, y: Float) {
                //点击对焦
                if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
                    val factory = viewFinder.createMeteringPointFactory(cameraSelector)
                    val point = factory.createPoint(x, y)
                    val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                        .setAutoCancelDuration(3, TimeUnit.SECONDS)
                        .build()
                    focusView.startFocus(Point(x.toInt(), y.toInt()))
                    val future: ListenableFuture<*> =
                        camera!!.cameraControl.startFocusAndMetering(action)
                    future.addListener(Runnable {
                        try {
                            val result = future.get() as FocusMeteringResult
                            if (result.isFocusSuccessful) {
                                focusView.onFocusSuccess()
                            } else {
                                focusView.onFocusFailed()
                            }
                        } catch (e: Exception) {
                            Log.e("", "", e)
                        }
                    }, cameraExecutor)
                }
            }

            override fun doubleClick(x: Float, y: Float) {
                //双击放大缩小
//                zoomState.value?.let {
//                    val currentZoomRatio = it.zoomRatio
//                    if (currentZoomRatio > it.minZoomRatio) {
//                        camera!!.cameraControl.setLinearZoom(0f)
//                    } else {
//                        camera!!.cameraControl.setLinearZoom(0.5f)
//                    }
//                }
            }

            override fun longClick(x: Float, y: Float) {}
        })
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT)
                    .show()
//                finish()
                sendVideo()
            }
        }
    }


    @SuppressLint("RestrictedApi")
    private fun startCamera() {
//        if (cameraExecutor != null) {
        cameraExecutor = Executors.newSingleThreadExecutor()
//        }
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable {
            cameraProvider = cameraProviderFuture.get()//获取相机信息

            //预览配置
            preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(viewFinder.createSurfaceProvider())
                }

            imageCapture = ImageCapture.Builder().build()//拍照用例配置

            val imageAnalyzer = ImageAnalysis.Builder()
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
                        Log.d(TAG, "Average luminosity: $luma")
                    })
                }

            //比特率
            videoCapture = VideoCapture.Builder()//录像用例配置
//                .setBitRate(3 * 1024 * 1024)较为清晰,且文件大小
                .setBitRate(900 * 1024)较为清晰,且文件大小为3.26M(30秒)
                .setVideoFrameRate(20)//帧率 视频帧率  越高视频体积越大
//                .setAudioBitRate(1024)//设置音频的码率
                .setTargetResolution(Size(720, 1080))//setTargetResolution设置生成的视频的分辨率
//                .setTargetResolution(Size(720,1080))//setTargetResolution设置生成的视频的分辨率
//                .setTargetAspectRatio(AspectRatio.RATIO_16_9) //设置高宽比
//                .setTargetRotation(viewFinder.display.rotation)//设置旋转角度
//                .setAudioRecordSource(AudioSource.MIC)//设置音频源麦克风
                .build()

            try {
                cameraProvider?.unbindAll()//先解绑所有用例
                camera = cameraProvider?.bindToLifecycle(
                    this,
                    cameraSelector,
                    preview,
                    imageCapture,
                    videoCapture
                )//绑定用例
            } catch (exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

            initCameraListener()
        }, ContextCompat.getMainExecutor(this))
    }

    //拍照
    private fun takePhoto() {
        val imageCapture = imageCapture ?: return

        val file = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path +
                    "/CameraX" + SimpleDateFormat(
                FILENAME_FORMAT,
                Locale.CHINA
            ).format(System.currentTimeMillis()) + ".jpg"
        )

        val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()

        imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(exc: ImageCaptureException) {
                    Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                }

                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    val savedUri = Uri.fromFile(file)
                    val msg = "Photo capture succeeded: $savedUri"
                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)
                }
            })
    }


    //录制视频
    @SuppressLint("RestrictedApi", "ClickableViewAccessibility")
    private fun takeVideo() {
        recorderStop.visibility = View.VISIBLE
        recorderStart.visibility = View.GONE
        //视频保存路径
        val file = File(localPath)
        //开始录像
        videoCapture?.startRecording(
            file,
            Executors.newSingleThreadExecutor(),
            object : VideoCapture.OnVideoSavedCallback {
                override fun onVideoSaved(@NonNull file: File) {
                    //保存视频成功回调,会在停止录制时被调用
                    LogUtil.d("onVideoSaved: file.absolutePath = " + file.absolutePath)
//                    finish()
//                    recorderStopAction(false)
//                    showSelectDialog(isTimeOver)
                    runOnUiThread {
                        LoadingFragment.dismiss()
                        showSelectDialog(isTimeOver)
                    }
//                  Toast.makeText(this@MainActivity, file.absolutePath, Toast.LENGTH_SHORT).show()
                }

                override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
                    //保存失败的回调,可能在开始或结束录制时被调用
                    Log.e("", "onError: $message")
                    runOnUiThread {
                        LoadingFragment.dismiss()
                        ToastUtils.showCenter("视频处理失败")
                        finish()
                    }
                }
            })


    }

    @SuppressLint("RestrictedApi")
    private fun stopRecording() {
        videoCapture?.stopRecording()//停止录制
        preview?.clear()//清除预览
        recorderStop.visibility = View.GONE
        recorderStart.visibility = View.VISIBLE
        chronometer.stop()
        chronometer.base = SystemClock.elapsedRealtime()
    }


    private fun recorderStopAction() {
        LogUtil.d("视频停止看看是否重复调用")
//        videoCapture.

//        recorderStop.setEnabled(false)
        // 停止拍摄
        var weakReference: WeakReference<Activity> = WeakReference(this)
        LoadingFragment.showLodingDialog(weakReference.get())
        stopRecording()


        //                btn_switch.setVisibility(View.VISIBLE);
    }

    private fun showSelectDialog(isTime: Boolean) {

        var msg = "是否发送视频"
        if (isTime) {
            msg = "录制时间已到" + maxMinuie / 60 + "分钟,是否发送视频"
        }
        if (alertDialogBuilder == null) {
            alertDialogBuilder = AlertDialog.Builder(this)
                .setMessage(msg)
                .setPositiveButton("确定") { dialog, which ->
                    dialog.dismiss()
                    sendVideo()
                }
                .setNegativeButton("取消") { dialog, which ->
                    if (localPath != null) {
                        val file: File = File(localPath)
                        if (file.exists()) file.delete()
                    }
                    finish()
                }.setCancelable(false)
        }
        alertDialogBuilder?.show()

    }

    fun sendVideo() {
        if (TextUtils.isEmpty(localPath)) {
            LogUtil.e( "recorder fail please try again!")
            return
        }
        setResult(Activity.RESULT_OK, intent.putExtra("uri", localPath))
        finish()
    }

    private fun stopOrSave() {

        val recordingTime = SystemClock.elapsedRealtime() - chronometer.base // 保存这次记录了的时间
        val second = Math.round(recordingTime.toFloat() / 1000).toFloat()
        LogUtil.d("second $second")
        try {
            if (second < minMinuie) {
                ToastUtils.showCenter("视频录制时间最少5秒")
                return
            }
            recorderStop.isEnabled = false
            recorderStart.isEnabled = false
            recorderStopAction()
        } catch (e: Exception) {
            ToastUtils.showCenter("操作异常,请返回后重试")
            e.printStackTrace()
            CrashReportUtil.getInstance().postException(e)
        }
    }

    override fun onPause() {
        super.onPause()
        if (progressDialog != null && progressDialog?.isShowing == true) {
            progressDialog?.dismiss()
        }

    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
    }

    @SuppressLint("RestrictedApi")
    override fun onDestroy() {
        super.onDestroy()
        alertDialogBuilder = null
        cameraExecutor.shutdown()
    }

    private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {

        private fun ByteBuffer.toByteArray(): ByteArray {
            rewind()
            val data = ByteArray(remaining())
            get(data)
            return data
        }

        override fun analyze(image: ImageProxy) {

            val buffer = image.planes[0].buffer
            val data = buffer.toByteArray()
            val pixels = data.map { it.toInt() and 0xFF }
            val luma = pixels.average()

            listener(luma)

            image.close()
        }
    }

    companion object {
        private const val TAG = "CameraXBasic"
        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = arrayOf(
            Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO
        )
    }

    override fun onBackPressed() {
//        super.onBackPressed()
        stopOrSave()
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.recorder_start -> {
                // 重置其他
                chronometer.base = SystemClock.elapsedRealtime()
                chronometer.start()
                takeVideo()
            }
            R.id.back,
            R.id.recorder_stop -> {
                isTimeOver = false;
                stopOrSave()
            }

        }
    }
}

项目地址.

gitee地址

https://gitee.com/liudao/camera-xvideo/tree/master

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

cameraX视频录制 拷贝直接用 的相关文章

  • 无线射频专题《射频合规,2.4GHz WIFI测试指标详解》

    目录 引言 Transmitter Power 发送功率 Transmit Spectrum Mask 发送信号频谱模版 Frequency Error 频率误差 EVM 矢量误差幅度 Band Edges and harmonics 频带
  • 获取当前访问的路径

    String returl 61 request getRequestURL 43 request getQueryString 61 61 null 34 34 34 34 43 request getQueryString
  • 抓取百度关键词排名

    最近在做百度关键词排名的功能 xff0c 发现网上资源比较少 xff0c 于是自己琢磨了一下 xff0c 写一下笔记 xff1b 本文重点在于提供思路 xff0c 请不要过分依赖 xff0c 本文主要靠抓取页面标签来完成 xff0c 如果百
  • 抓取百度关键词排名、标题、连接、描述

    抓取百度关键词排名 标题 连接 描述 转载请标明出处 最近在做百度关键词排名的功能 xff0c 发现网上资源比较这里写代码片少 xff0c 于是自己琢磨了一下 xff0c 写一下笔记 xff1b 本文重点在于提供思路 xff0c 请不要过分
  • Windows Server 2008远程桌面端口的修改

    Windows Server 2008远程桌面端口系统默认的是3389端口 xff0c 但出于安全考虑 xff08 谁都不希望任何都可以远程连接到自己的服务器吧 xff0c 哈哈 xff09 xff0c 经常我们把系统默认的3389端口更改
  • jsp 登陆成功后,显示登录的用户名

    首先在登陆界面将用户名保存起来 xff0c 我这里是将用户名提交到Servlet然后再进行保存 xff0c 从登录界面取用户参数 String uname 61 request getParameter 34 userName 34 req
  • 背景图片随网页的变化而变化(指大小)

    lt DOCTYPE HTML PUBLIC 34 W3C DTD HTML 4 01 Transitional EN 34 gt lt html xmlns 61 34 http www w3 org 1999 xhtml 34 gt l
  • 为什么vscode用久了电脑速度变慢?

    1 vscode 插件占用的内存 1 已经安装的插件 2 插件占用内存 我可以看到 xff0c vs code 这个程序下面有多个进程在跑 xff0c 插件占用的内存比我电脑剩下所有占用的内存还要高 xff0c 但是vs code 性能还是
  • ROS基础学习(一)---创建工作空间

    工作空间 xff08 workspace xff1a 是一个存放工程开发相关文件的文件夹 src xff1a 代码空间 xff08 Source Space build 编译空间 xff08 Build Space devel 开发空间 x
  • 一个简单的爬虫程序,爬取网站的图片

    最简单的爬虫是分析网页 xff0c 如果要爬取图片 xff0c 就要将图片在网页中的格式进行分析 xff0c 取到图片的连接 xff0c 接着下载图片 xff1b 由于网页中还会链接到其他的网页 xff0c 所以需要将其中的所有网页取出 x
  • python文件操作及引申的代码行数统计

    文件操作一般包括文件的读写 xff0c 文件夹的创建 xff0c 文件夹的删除等 第一部分 xff1a python文件读写的基本操作 python打开文件一般使用 open函数 xff1a open file mode 61 39 r 3
  • 超声波测距仪

    基本原理 超声波是利用反射的原理测量距离的 xff0c 被测距离一端为超声波传感器 xff0c 另一端必须有能反射超声波的物体 测量距离时 xff0c 将超声波传感器对准反射物发射超声波 xff0c 并开始计时 xff0c 超声波在空气中传
  • Authorization 值中Bearer空格加token值在python接口请求中如何实现

    在项目中每个接口请求都需要Authorization 值 xff0c 而Authorization他的值必须 Bearer 加token值 xff0c 刚开始自己忘记添加Bearer 接口请求一直拒绝访问 xff0c 后来用fiddler抓
  • printf格式化字符串漏洞原理解析

    读任意地址 printf 34 x 34 只给格式化字符串 xff0c 而不给参数 xff0c 会导致内存泄漏从而读到内存中其他地址的数据 N x参数可以以16进制方式打印第N个参数的内容 xff0c 通过修改N xff0c 我们可以遍历栈
  • 教程丨利用微软官方工具制作U盘安装Win10系统

    一 制作Win10安装U盘 1 登录网站 https www microsoft com zh cn software download windows10 下载 MediaCreationTool 工具 xff0c 这里我们直接点击 立即

随机推荐

  • Linux常用命令大全

    发布jar包 nohup java jar xxxx jar gt dev null 2 gt amp 1 amp 修改nginx conf 后刷新配置 usr local nginx sbin nginx t 测试配置文件修改是否正常 u
  • Zab协议详解

    什么是Zab协议 xff1f Zab协议 的全称是 Zookeeper Atomic Broadcast xff08 Zookeeper原子广播 xff09 Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性 Zab协议是
  • 谷歌浏览器安装json格式化插件

    实际开发工作中经常用到json数据 xff0c 那么就会有这样一个需求 xff1a 在谷歌浏览器中访问URL地址返回的json数据能否按照json格式展现出来 比如 xff0c 在谷歌浏览器中访问 xff1a http jsonview c
  • Seata详解(一)

    分布式事务 事务是数据库的概念 xff0c 数据库事务 xff08 ACID xff1a 原子性 一致性 隔离性和持久性 xff09 xff1b 分布式事务的产生 xff0c 是由于数据库的拆分和分布式架构 微服务 带来的 xff0c 在常
  • RocketMQ的事务消息和改造

    什么是 rmq分布式事务消息 xff1f Apache RocketMQ在4 3 0版中已经支持分布式事务消息 xff0c 这里RocketMQ采用了2PC的思想来实现了提交事务消息 xff0c 同时增加一个补偿逻辑来处理二阶段超时或者失败
  • session和token区别

    一 session的状态保持及弊端 xff08 1 xff09 当用户第1次通过浏览器使用用户名和密码访问服务器时 xff0c 服务器对用户名和密码进行验证 xff08 2 xff09 验证成功后 xff0c 在服务器端生成并保存sessi
  • 关于阿里云对RocketMQ的topic,tag,consumer关系的理解

    什么是订阅关系一致 消息队列RocketMQ版 阿里云帮助中心 RocketMQ 中Topic Tag GroupName基本概念介绍 Young丶的博客 CSDN博客 rocketmq topic和group的区别 转载
  • MySql Workbench 8.0汉化插件分享

    打开workbench的安装数据目录 xff0c 路径是 xff1a C Program Files MySQL MySQL Workbench 8 0 CE data 打开以后 xff0c 可以看到下面有一堆的xml结尾的文件 xff0c
  • Java-Jdk12版本下载后没有Jre的解决方案以及配置环境变量

    新本Jdk12版本下载后不像以前的版本会自动生成Jre文件夹 xff0c 通过dos命令行可以解决 1 下载jdk12 网址 xff1a https www oracle com technetwork java javase downlo
  • android--性能优化1--首屏优化&启动速度与执行效率检测

    文章目录 实战篇traceview 工具使用操作步骤操作步骤2 systrace 工具使用操作步骤 AOP 工具 hugo 的使用使用方法效果 BlockCanary 使用AOP 工具使用 AspectJ操作步骤 优化方案 异步线程优化 针
  • error Failed connect to github.com 443原因 Github更换认证方式

    Github更换认证方式 github近期调整了认证方式 xff0c 不再允许第三方工具基于账号密码来访问和管理项目了 除非使用github DeskTop 自带的客户端 好坑啊 我一直再用android studio 自带的提交工具很方便
  • AppCompatRatingBar备份用 下次拷贝直接用自定义图片背景

    控件 lt androidx appcompat widget AppCompatRatingBar android id 61 34 64 43 id rating bar 34 style 61 34 64 style myRating
  • 备份Edittext编辑框字数限制ui以及逻辑

    ui布局 etContent addTextChangedListener new TextWatcher 记录输入的字数 private CharSequence enterWords private int selectionStart
  • android—性能优化2—内存优化

    文章目录 性能优化 工具 memory profilerLeakCanaryarthookepic 库 java内存管理机制java 内存回收机制Android内存管理机制Dalvik与 Art区别Low Memory Killer 内存抖
  • glide4.11.0封装gfilib优化gif图片加载

    文章目录 具体步骤 下载giflib 和 framesequence导入并集成 giflib 和 framesequenceglide的配置gif使用giflib集成开始使用加载思路创建自定义GifDrawable 需求 目前项目中加载进度
  • java筑基.泛型,反射,注解-利用注解加反射练习

    文章目录 泛型 泛型类泛型方法泛型接口子类明确泛型类的类型参数变量子类不明确泛型类的类型参数变量 限定类型变量通配符泛型 注解元注解注解的应用场景 反射 注解 43 反射练习 泛型 把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特
  • android性能优化实践与总结(包含启动,内存优化)

    应用中性能优化实践与总结 精心总结 任何优化都需要进行检测 以数据说话 优化前和优化后有了怎样的提升 TOC 启动优化 检测启动时间 检测工具任选其一 hugo 插件 自己定义时间开始和结束手动计算时间 AOP 工具 AspectJ adb
  • android—性能优化3—网络优化

    文章目录 网络优化正确的认识流量消耗网络优化维度其他网络请求误区 网络优化工具选择Network Proifiler抓包工具stetho流量优化如何判断APP流量消耗偏高如何测试 测试方案 线上线下流量获取线上流量获取方案NetworkSt
  • java 实现死锁

    资源抢占 导致死锁 public static void main String args final Object a 61 new Object final Object b 61 new Object Thread threadA 6
  • cameraX视频录制 拷贝直接用

    文章目录 效果图activity代码项目地址 最下面是GIT 地址 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img 3i0EaImv 1637722081187 https liudao01 github io