Android 不申请权限储存、删除相册图片

Android 不申请权限储存、删除相册图片

前言

最近重新看了下安卓的储存适配,并结合之前做的拍照、裁切demo,小小实验了一下。Android 6.0增加了动态文件权限申请; Android 7.0需要使用FileProvider来获取Uri,不能直接使用file获得; Android 10.0引入了分区储存,并在Android 11.0中强制使用。

Java的File变得越来越难使用,那安卓提供的 SAF(存储访问框架)和 Uri(ContentProvider)是不是得学起来?SAF通过intent的GET_CONTENT action使用系统界面选择文件,Uri则是ContentProvider提供的路径。

开发的时候不知道读者有没有很迷惑到底什么地方应该开启储存权限,不开启储存权限能否储存、删除相册图片呢?下面我们结合代码试试。

知识储备

前面我已经发了一篇关于拍照、裁切的博文,里面可以方便地获得bitmap,下面内容里就不详细叙述了,博文链接如下:

安卓拍照、裁切、选取图片实践

关于文件权限适配的可以看下面几篇文章:

Android 存储基础

Android 10、11 存储完全适配(上)

Android 10、11 存储完全适配(下)

导出图片到相册

上篇文章我们通过拍照获得了新图片,但是也仅仅是保存在外部储存的私有目录里,如果删除了应用,图片也随之被删除了,那这样是不行的,应该考虑如何把图片保存到系统相册去。要是以前,我就直接申请权限,直接Environment拿到根目录,使用File直接保存过去就行了,但是这样好吗?想想我们手机里面一大堆的目录,这个文件的随便使用,是不是有问题?是不是违背了Android的规范?那应该怎么做呢?请看下面代码:

    // 保存到外部储存-公有目录-Picture内,并且无需储存权限
    private fun insert2Pictures() {
        binding.image.drawable?.let {
            val bitmap = it.toBitmap()
            val baos = ByteArrayOutputStream()
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
            val bais = ByteArrayInputStream(baos.toByteArray())
            insert2Album(bais, "Media")
            showToast("导出到相册成功")
        }
    }

    // 使用MediaStore方式将流写入相册
    @Suppress("SameParameterValue")
    private fun insert2Album(inputStream: InputStream, type: String) {
        val fileName = "${type}_${System.currentTimeMillis()}.jpg"
        val contentValues = ContentValues()
        contentValues.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName)
        // Android 10,路径保存在RELATIVE_PATH
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            //RELATIVE_PATH 字段表示相对路径,Fundark为相册下专有目录
            contentValues.put(
                MediaStore.Images.ImageColumns.RELATIVE_PATH,
                Environment.DIRECTORY_PICTURES + File.separator + "Fundark"
            )
        } else {
            val dstPath = StringBuilder().let { sb->
                sb.append(requireContext()
                    .getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!.path)
                sb.append(File.separator)
                sb.append("Fundark")
                sb.append(File.separator)
                sb.append(fileName)
                sb.toString()
            }

            //DATA字段在Android 10.0 之后已经废弃(Android 11又启用了,但是只读)
            contentValues.put(MediaStore.Images.ImageColumns.DATA, dstPath)
        }

        // 插入相册
        val uri =  requireContext().contentResolver
            .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

        // 写入文件
        uri?.let {
            write2File(it, inputStream)
        }
    }

    private fun write2File(uri: Uri, inputStream: InputStream) {
        // 从Uri构造输出流
        requireContext().contentResolver.openOutputStream(uri)?.use { outputStream->
            val byteArray = ByteArray(1024)
            var len: Int
            do {
                //从输入流里读取数据
                len = inputStream.read(byteArray)
                if (len != -1) {
                    outputStream.write(byteArray, 0, len)
                    outputStream.flush()
                }
            } while (len != -1)
        }
    }

这里的代码也只是示例,关于Exception的部分没做,但是关键在于我们没有申请权限就把图片保存到相册去了,现在打开你的相册就能发现你保存的图片,点开详细信息就能看到它的实际位置:

pic

删除相册图片

刚开始我也绝对这里可能需要申请系统的储存权限,不然怎么可以去删除外部相册的图片呢?实际上,这里也是又限制的,你可以删除你自己创建的图片,这样一说是不是又很合理了。下面试试:

    private fun clearAppPictures() {
        val selection: String
        val selectionArgs: String
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            selection = "${MediaStore.Images.ImageColumns.RELATIVE_PATH} like ?"
            selectionArgs = "%" + Environment.DIRECTORY_PICTURES + File.separator + "Fundark" + "%"
        } else {
            val dstPath = StringBuilder().let { sb->
                sb.append(requireContext()
                    .getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!.path)
                sb.append(File.separator)
                sb.append("Fundark")
                sb.toString()
            }
            selection = "${MediaStore.Images.ImageColumns.DATA} like ?"
            selectionArgs = "%$dstPath%"
        }
        val num = requireContext().contentResolver.delete(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            selection,
            arrayOf(selectionArgs)
        )

        showToast("删除本应用相册图片${num}张")
    }

还是调用ContentProvider处理的,稍微处理下条件,就能完成删除。不过有意思的是,这里删除图片会被系统拦截,荣耀10提示删除了相册图片,并会将图片放到系统回收站去,实际这样也还行。

完整代码

上篇博客的代码和这篇博客的代码放一起,希望对读者有用:

import android.Manifest
import android.app.Activity.RESULT_OK
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.toBitmap
import androidx.core.util.Consumer
import com.silencefly96.module_base.base.BaseFragment
import com.silencefly96.module_base.base.IPermissionHelper
import com.silencefly96.module_hardware.databinding.FragmentTakePhotoBinding
import java.io.*


class TakePhotoFragment : BaseFragment() {

    companion object{
        const val REQUEST_CAMERA_CODE = 1
        const val REQUEST_ALBUM_CODE = 2
        const val REQUEST_CROP_CODE = 3

        const val MAX_WIDTH = 480
        const val MAX_HEIGHT = 720
    }

    private var _binding: FragmentTakePhotoBinding? = null
    private val binding get() = _binding!!

    // 文件路径
    private var picturePath: String = ""

    // 裁切路径
    private var cropPicPath: String = ""

    // 启用裁切
    private var enableCrop: Boolean = true

    // 绑定布局
    override fun bindView(inflater: LayoutInflater, container: ViewGroup?): View {
        _binding = FragmentTakePhotoBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun doBusiness(context: Context?) {
        binding.takePhoto.setOnClickListener {
            requestPermission { openCamera() }
        }

        binding.pickPhoto.setOnClickListener {
            openAlbum()
        }

        binding.insertPictures.setOnClickListener {
            insert2Pictures()
        }

        binding.clearCache.setOnClickListener {
            clearCachePictures()
        }

        binding.clearPictures.setOnClickListener {
            clearAppPictures()
        }

        binding.cropSwitch.setOnCheckedChangeListener { _, isChecked -> enableCrop = isChecked}
    }

    private fun requestPermission(consumer: Consumer<Boolean>) {
        // 动态申请权限,使用的外部私有目录无需申请权限
        requestRunTimePermission(requireActivity(), arrayOf(
            Manifest.permission.CAMERA,
//            Manifest.permission.WRITE_EXTERNAL_STORAGE
        ),
            object : IPermissionHelper.PermissionListener {
                override fun onGranted() {
                    consumer.accept(true)
                }

                override fun onGranted(grantedPermission: List<String>?) {
                    consumer.accept(false)
                }

                override fun onDenied(deniedPermission: List<String>?) {
                    consumer.accept(false)
                }
            })
    }

    private fun openCamera() {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        // 应用外部私有目录:files-Pictures
        val picFile = createFile("Camera")
        val photoUri = getUriForFile(picFile)
        // 保存路径,不要uri,读取bitmap时麻烦
        picturePath = picFile.absolutePath
        // 给目标应用一个临时授权
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        //android11以后强制分区存储,外部资源无法访问,所以添加一个输出保存位置,然后取值操作
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
        startActivityForResult(intent, REQUEST_CAMERA_CODE)
    }

    private fun createFile(type: String): File {
        // 在相册创建一个临时文件
        val picFile = File(requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),
                "${type}_${System.currentTimeMillis()}.jpg")
        try {
            if (picFile.exists()) {
                picFile.delete()
            }
            picFile.createNewFile()
        } catch (e: IOException) {
            e.printStackTrace()
        }

        // 临时文件,后面会加long型随机数
//        return File.createTempFile(
//            type,
//            ".jpg",
//            requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
//        )

        return picFile
    }

    private fun getUriForFile(file: File): Uri {
        // 转换为uri
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //适配Android 7.0文件权限,通过FileProvider创建一个content类型的Uri
            FileProvider.getUriForFile(
                requireActivity(),
                "com.silencefly96.module_hardware.fileProvider", file
            )
        } else {
            Uri.fromFile(file)
        }
    }

    private fun openAlbum() {
        val intent = Intent()
        intent.type = "image/*"
        intent.action = "android.intent.action.GET_CONTENT"
        intent.addCategory("android.intent.category.OPENABLE")
        startActivityForResult(intent, REQUEST_ALBUM_CODE)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == RESULT_OK) {
            when(requestCode) {
                REQUEST_CAMERA_CODE -> {
                    // 通知系统文件更新
//                    requireContext().sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
//                        Uri.fromFile(File(picturePath))))
                    if (!enableCrop) {
                        val bitmap = getBitmap(picturePath)
                        bitmap?.let {
                            // 显示图片
                            binding.image.setImageBitmap(it)
                        }
                    }else {
                        cropImage(picturePath)
                    }
                }
                REQUEST_ALBUM_CODE -> {
                    data?.data?.let { uri ->
                        if (!enableCrop) {
                            val bitmap = getBitmap("", uri)
                            bitmap?.let {
                                // 显示图片
                                binding.image.setImageBitmap(it)
                            }
                        }else {
                            cropImage(uri)
                        }
                    }
                }
                REQUEST_CROP_CODE -> {
                    val bitmap = getBitmap(cropPicPath)
                    bitmap?.let {
                        // 显示图片
                        binding.image.setImageBitmap(it)
                    }
                }
            }
        }
    }

    private fun getBitmap(path: String, uri: Uri? = null): Bitmap? {
        var bitmap: Bitmap?
        val options = BitmapFactory.Options()
        // 先不读取,仅获取信息
        options.inJustDecodeBounds = true
        if (uri == null) {
            BitmapFactory.decodeFile(path, options)
        }else {
            val input = requireContext().contentResolver.openInputStream(uri)
            BitmapFactory.decodeStream(input, null, options)
        }

        // 预获取信息,大图压缩后加载
        val width = options.outWidth
        val height = options.outHeight
        Log.d("TAG", "before compress: width = " +
                options.outWidth + ", height = " + options.outHeight)

        // 尺寸压缩
        var size = 1
        while (width / size >= MAX_WIDTH || height / size >= MAX_HEIGHT) {
            size *= 2
        }
        options.inSampleSize = size
        options.inJustDecodeBounds = false
        bitmap = if (uri == null) {
            BitmapFactory.decodeFile(path, options)
        }else {
            val input = requireContext().contentResolver.openInputStream(uri)
            BitmapFactory.decodeStream(input, null, options)
        }
        Log.d("TAG", "after compress: width = " +
                options.outWidth + ", height = " + options.outHeight)

        // 质量压缩
        val baos = ByteArrayOutputStream()
        bitmap!!.compress(Bitmap.CompressFormat.JPEG, 80, baos)
        val bais = ByteArrayInputStream(baos.toByteArray())
        options.inSampleSize = 1
        bitmap = BitmapFactory.decodeStream(bais, null, options)

        return bitmap
    }

    private fun cropImage(path: String) {
        cropImage(getUriForFile(File(path)))
    }

    private fun cropImage(uri: Uri) {
        val intent = Intent("com.android.camera.action.CROP")
        // Android 7.0需要临时添加读取Url的权限
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
//        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
        intent.setDataAndType(uri, "image/*")
        // 使图片处于可裁剪状态
        intent.putExtra("crop", "true")
        // 裁剪框的比例(根据需要显示的图片比例进行设置)
//        if (Build.MANUFACTURER.contains("HUAWEI")) {
//            //硬件厂商为华为的,默认是圆形裁剪框,这里让它无法成圆形
//            intent.putExtra("aspectX", 9999)
//            intent.putExtra("aspectY", 9998)
//        } else {
//            //其他手机一般默认为方形
//            intent.putExtra("aspectX", 1)
//            intent.putExtra("aspectY", 1)
//        }

        // 设置裁剪区域的形状,默认为矩形,也可设置为圆形,可能无效
        // intent.putExtra("circleCrop", true);
        // 让裁剪框支持缩放
        intent.putExtra("scale", true)
        // 属性控制裁剪完毕,保存的图片的大小格式。太大会OOM(return-data)
//        intent.putExtra("outputX", 400)
//        intent.putExtra("outputY", 400)

        // 生成临时文件
        val cropFile = createFile("Crop")
        // 裁切图片时不能使用provider的uri,否则无法保存
//        val cropUri = getUriForFile(cropFile)
        val cropUri = Uri.fromFile(cropFile)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri)
        // 记录临时位置
        cropPicPath = cropFile.absolutePath

        // 设置图片的输出格式
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())

        // return-data=true传递的为缩略图,小米手机默认传递大图, Android 11以上设置为true会闪退
        intent.putExtra("return-data", false)

        startActivityForResult(intent, REQUEST_CROP_CODE)
    }

    // 保存到外部储存-公有目录-Picture内,并且无需储存权限
    private fun insert2Pictures() {
        binding.image.drawable?.let {
            val bitmap = it.toBitmap()
            val baos = ByteArrayOutputStream()
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
            val bais = ByteArrayInputStream(baos.toByteArray())
            insert2Album(bais, "Media")
            showToast("导出到相册成功")
        }
    }

    // 使用MediaStore方式将流写入相册
    @Suppress("SameParameterValue")
    private fun insert2Album(inputStream: InputStream, type: String) {
        val fileName = "${type}_${System.currentTimeMillis()}.jpg"
        val contentValues = ContentValues()
        contentValues.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName)
        // Android 10,路径保存在RELATIVE_PATH
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            //RELATIVE_PATH 字段表示相对路径,Fundark为相册下专有目录
            contentValues.put(
                MediaStore.Images.ImageColumns.RELATIVE_PATH,
                Environment.DIRECTORY_PICTURES + File.separator + "Fundark"
            )
        } else {
            val dstPath = StringBuilder().let { sb->
                sb.append(requireContext()
                    .getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!.path)
                sb.append(File.separator)
                sb.append("Fundark")
                sb.append(File.separator)
                sb.append(fileName)
                sb.toString()
            }

            //DATA字段在Android 10.0 之后已经废弃(Android 11又启用了,但是只读)
            contentValues.put(MediaStore.Images.ImageColumns.DATA, dstPath)
        }

        // 插入相册
        val uri =  requireContext().contentResolver
            .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

        // 写入文件
        uri?.let {
            write2File(it, inputStream)
        }
    }

    private fun write2File(uri: Uri, inputStream: InputStream) {
        // 从Uri构造输出流
        requireContext().contentResolver.openOutputStream(uri)?.use { outputStream->
            val byteArray = ByteArray(1024)
            var len: Int
            do {
                //从输入流里读取数据
                len = inputStream.read(byteArray)
                if (len != -1) {
                    outputStream.write(byteArray, 0, len)
                    outputStream.flush()
                }
            } while (len != -1)
        }
    }

    private fun clearCachePictures() {
        // 外部储存-私有目录-files-Pictures目录
        requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.let { dir->
            // 删除其中的图片
            try {
                val pics = dir.listFiles()
                pics?.forEach { pic ->
                    pic.delete()
                }
                showToast("清除缓存成功")
            }catch (e: Exception) {
                e.printStackTrace()
                showToast("清除缓存失败")
            }
        }
    }

    private fun clearAppPictures() {
        val selection: String
        val selectionArgs: String
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            selection = "${MediaStore.Images.ImageColumns.RELATIVE_PATH} like ?"
            selectionArgs = "%" + Environment.DIRECTORY_PICTURES + File.separator + "Fundark" + "%"
        } else {
            val dstPath = StringBuilder().let { sb->
                sb.append(requireContext()
                    .getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!.path)
                sb.append(File.separator)
                sb.append("Fundark")
                sb.toString()
            }
            selection = "${MediaStore.Images.ImageColumns.DATA} like ?"
            selectionArgs = "%$dstPath%"
        }
        val num = requireContext().contentResolver.delete(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            selection,
            arrayOf(selectionArgs)
        )

        showToast("删除本应用相册图片${num}张")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

}

里面的BaseActivity读者自己改下。

<?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=".camera.TakePhotoFragment">

    <Button
        android:id="@+id/takePhoto"
        android:text="@string/take_photo_by_system"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="10dp"
        />

    <Button
        android:id="@+id/pickPhoto"
        android:text="@string/pick_photo_by_system"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/takePhoto"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="10dp"
        />

    <Button
        android:id="@+id/insertPictures"
        android:text="@string/insert_photo_to_pictures"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/pickPhoto"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="10dp"
        />

    <Button
        android:id="@+id/clearCache"
        android:text="@string/clear_Cache"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/insertPictures"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/clearPictures"
        android:layout_marginTop="10dp"
        />

    <Button
        android:id="@+id/clearPictures"
        android:text="@string/clear_this_pictures"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/insertPictures"
        app:layout_constraintLeft_toRightOf="@+id/clearCache"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="10dp"
        />

    <TextView
        android:id="@+id/enable_crop_text"
        android:textSize="18sp"
        android:text="@string/enable_crop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="@id/cropSwitch"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/cropSwitch"
        app:layout_constraintBottom_toBottomOf="@id/cropSwitch"
        />

    <androidx.appcompat.widget.SwitchCompat
        android:id="@+id/cropSwitch"
        android:checked="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        app:layout_constraintLeft_toRightOf="@id/enable_crop_text"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/clearPictures"
        />

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/cropSwitch"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="10dp"
        tools:ignore="ContentDescription"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/2287.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FPGA基于RIFFA实现PCIE采集HDMI传输,提供工程源码和QT上位机

目录1、前言2、RIFFA理论基础3、设计思路和架构4、vivado工程详解5、上板调试验证并演示6、福利&#xff1a;工程代码的获取1、前言 PCIE是目前速率很高的外部板卡与CPU通信的方案之一&#xff0c;广泛应用于电脑主板与外部板卡的通讯&#xff0c;PCIE协议极其复杂&#xff0c…

【CS224W】(task12)GAT GNN training tips

note GAT使用attention对线性转换后的节点进行加权求和&#xff1a;利用自身节点的特征向量分别和邻居节点的特征向量&#xff0c;进行内积计算score。异质图的消息传递和聚合&#xff1a;hv(l1)σ(∑r∈R∑u∈Nvr1cv,rWr(l)hu(l)W0(l)hv(l))\mathbf{h}_v^{(l1)}\sigma\left(\…

第十八天 Vue-前端工程化总结

目录 Vue-前端工程化 1. 前后端分离开发 1.1 介绍 1.2 Yapi 2. 前端工程化 2.1 环境准备 2.2 Vue项目简介 2.3 Vue项目开发流程 3. Vue组件库Element 3.1 快速入门 3.2 常用组件 3.3 案例 Vue-前端工程化 前面我们已经讲解了HTML、CSS、JavaScript以及Vue等知识。已…

【粉丝投稿】上海某大厂的面试题,岗位是测开(25K*16)

简单介绍一句&#xff0c;大专出身&#xff0c;三年经验。跳了四次槽&#xff0c;面试了无数次&#xff0c;现在把自己的面试经验整理出来分享给大家&#xff0c;堪称必杀技&#xff01; 1&#xff0c;一切从实际出发&#xff0c;对实际工作进行适当修饰 2&#xff0c;不会的简…

【进阶数据结构】平衡搜索二叉树 —— AVL树

&#x1f308;感谢阅读East-sunrise学习分享——[进阶数据结构]AVL树 博主水平有限&#xff0c;如有差错&#xff0c;欢迎斧正&#x1f64f;感谢有你 码字不易&#xff0c;若有收获&#xff0c;期待你的点赞关注&#x1f499;我们一起进步&#x1f680; &#x1f308;我们上一篇…

学习Linux只要学会这个命令就够了!

大家好&#xff0c;我是良许。 这段时间又是搬家&#xff0c;又是找新办公室&#xff0c;现在终于安顿下来了&#xff0c;有时间给大家分享干货了。 今天给大家介绍一个 Linux 超级实用命令&#xff0c;有了这个命令&#xff0c;你就可以愉快使用 Linux 上几乎所有常用命令了…

【Unity入门】3D物体

【Unity入门】3D物体 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;物体移动旋转缩放 &#xff08;1&#xff09;物体移动 在上一篇文章【Unity入门】场景视图操作我们学会了在场景中创建3…

Java现在好找工作吗?

Java到2023年已经28岁了&#xff0c;可能你会怀疑它是否还一如当年一样的强大&#xff0c;在应用层领域独占鳌头。但是基于Java庞大的市场占有率和需求&#xff0c;它依然在保持着更新迭代&#xff0c;依然是最常用的底层开发语言&#xff0c;基于其安全性、开放性、稳定性和跨…

springboot Aspect切面

问题描述 配置切面&#xff0c;但未切到目标类上 切面类 Component Aspect public class ControllerAspect {//Pointcut("execution(* com.yzk.learn.springbootsecurity.controller.UserController.info(..))")Pointcut("execution(* com.learn..*.controlle…

类ChatGPT开源项目的部署与微调:从LLaMA到ChatGLM-6B

前言 近期&#xff0c;除了研究ChatGPT背后的各种技术细节 不断看论文(至少100篇&#xff0c;100篇目录见此&#xff1a;ChatGPT相关技术必读论文100篇)&#xff0c;还开始研究一系列开源模型(包括各自对应的模型架构、训练方法、训练数据、本地私有化部署、硬件配置要求、微调…

Java代码是如何被CPU狂飙起来的?

&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3; &#x1f38d;大家好&#xff0c;我是慕枫 &#x1f38d;前阿里巴巴高级工程师&#xff0c;InfoQ签约作者、阿里云专家博主&#xff0c;一直致力于用大白话讲解技术知识 &#x…

安全防御之防火墙篇(二)

目录 1.防火墙如何处理双通道协议&#xff1f; 2.防火墙如何处理NAT&#xff1f; 3.防火墙支持哪些NAT技术&#xff0c;主要应用的场景是什么&#xff1f; 4.当内网PC通过公网域名解析访问内网服务器的时候&#xff0c;会存在什么问题&#xff0c;如何解决&#xff1f;请详细…

【MySQL】CentOS编译安装MySQL5.7实战

前言 这篇文章是关于MySQL编译安装的&#xff0c;重点掌握的是编译的过程&#xff0c;以及体会排错的痛苦。出错在所难免&#xff0c;最重要的是要有一颗不放弃的心。 本文收录于《数据库入门与精通》专栏, 本专栏写作的过程中&#xff0c;联合了csdn几位DBA大佬&#xff0c;…

SpringBoot整合Kafka(包含Kafka_2.12-3.3.1单节点安装,kafka可视化程序efak v3.0.1安装)

SpringBoot整合Kafka&#xff08;包含Kafka_2.12-3.3.1单节点安装&#xff0c;kafka可视化程序efka v3.0.1安装&#xff09;kafka、efak安装包下载kafka安装资源下载&#xff1a;下载tgz安装包&#xff1a;http://archive.apache.org/dist/kafka/ //解压 tar -zxvf /home/soft/…

自定义类型的超详细讲解ᵎᵎ了解结构体和位段这一篇文章就够了ᵎ

目录 1.结构体的声明 1.1基础知识 1.2结构体的声明 1.3结构体的特殊声明 1.4结构体的自引用 1.5结构体变量的定义和初始化 1.6结构体内存对齐 那对齐这么浪费空间&#xff0c;为什么要对齐 1.7修改默认对齐数 1.8结构体传参 2.位段 2.1什么是位段 2.2位段的内存分配…

【java】笔试强训Day1

⛳选择题 1.在 Java 中&#xff0c;存放字符串常量的对象属于 &#xff08; &#xff09;类对象 A、Character B、String C、StringBuffer D、Vector &#x1f648;大家觉得答案是什么呢 &#x1f649;答案是…

GPT-4发布:人工智能新高度,以图生文技术震撼,短时间内挤爆OpenAI模型付费系统

“GPT-4&#xff0c;起飞&#xff01;”今日凌晨1点&#xff0c;OpenAI正式推出史上最强大的GPT-4文本生成AI系统 GPT-4&#xff1a;人工智能的新里程碑 你可能已经听说过GPT-3&#xff0c;它是一种能够生成自然语言文本的强大模型&#xff0c;可以用来回答问题、写文章、编程…

【Java SE】变量的本质

目录一. 前言二. 变量(variable)2.1 性质2.2 变量类型2.2.1 核心区别2.3 变量的使用三. 总结一. 前言 一天一个Java小知识点&#xff0c;助力小伙伴更好地入门Java&#xff0c;掌握更深层次的语法。 二. 变量(variable) 2.1 性质 变量本质上就是代表一个”可操作的存储空间”…

STL库中list的迭代器实现痛点分析

前文本篇文章准备换个模式&#xff0c;之前都是先详解模拟实现&#xff0c;但是模拟实现的基本逻辑大多数老铁都是明白的&#xff0c;所以我们这次主要讲解STL库中list的独特性&#xff0c;也就是模拟实现中的重难点文末有模拟实现的源码一&#xff0c;list实现的特殊类list实现…

【pytorch】使用deepsort算法进行目标跟踪,原理+pytorch实现

目录deepsort流程一、匈牙利算法二、卡尔曼滤波车速预测例子动态模型的概念卡尔曼滤波在deepsort中的动态模型三、预测值及测量值的含义deepsort在pytorch中的运行deepsort流程 DeepSORT是一种常用的目标跟踪算法&#xff0c;它结合了深度学习和传统的目标跟踪方法。DeepSORT的…