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_PATHif (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: Intdo {//从输入流里读取数据len = inputStream.read(byteArray)if (len != -1) {outputStream.write(byteArray, 0, len)outputStream.flush()}} while (len != -1)}}

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

删除相册图片

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

    private fun clearAppPictures() {val selection: Stringval selectionArgs: Stringif (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 = 1const val REQUEST_ALBUM_CODE = 2const val REQUEST_CROP_CODE = 3const val MAX_WIDTH = 480const val MAX_HEIGHT = 720}private var _binding: FragmentTakePhotoBinding? = nullprivate 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-Picturesval 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 {// 转换为urireturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//适配Android 7.0文件权限,通过FileProvider创建一个content类型的UriFileProvider.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 = trueif (uri == null) {BitmapFactory.decodeFile(path, options)}else {val input = requireContext().contentResolver.openInputStream(uri)BitmapFactory.decodeStream(input, null, options)}// 预获取信息,大图压缩后加载val width = options.outWidthval height = options.outHeightLog.d("TAG", "before compress: width = " +options.outWidth + ", height = " + options.outHeight)// 尺寸压缩var size = 1while (width / size >= MAX_WIDTH || height / size >= MAX_HEIGHT) {size *= 2}options.inSampleSize = sizeoptions.inJustDecodeBounds = falsebitmap = 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 = 1bitmap = 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_PATHif (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: Intdo {//从输入流里读取数据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: Stringval selectionArgs: Stringif (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"><Buttonandroid: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"/><Buttonandroid: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"/><Buttonandroid: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"/><Buttonandroid: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"/><Buttonandroid: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"/><TextViewandroid: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.SwitchCompatandroid: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"/><ImageViewandroid: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>

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

  1. Android之如何分析手机系统相册图片和视频删除后保存的位置然后恢复文件,目前已经适配小米、OPPO、VIVO、一加、努比亚、魅族等手机。

    1 需求 需要获取各种型号手机系统相册图片和视频删除后保存的位置 2 分析 1)我们可以通过在sdcard目录下进行相关查找文件夹关键字,对 "cycle"或者"tras ...

  2. Android之如何分析手机系统相册图片和视频删除后保存的位置

    1 需求 需要获取各种型号手机系统相册图片和视频删除后保存的位置 2 分析 1)我们可以通过在sdcard目录下进行相关查找文件夹关键字,对 "cycle"或者"tras ...

  3. 处理android11以上无法删除相册图片的问题

    在android11手机上,当APP重新安装后,无法删除之前在APP上保存的图片,是因为没有app没有修改这张图片的权限,所以需要重新手动申请权限,先上图: 功能代码实现: 1.创建一个fragmen ...

  4. Unity2019中的android动态申请权限(Permissions)

    请使用最新文章: Unity2022中的android权限处理(Permissions) 动态权限,权限弹窗 注意事项: 1.因为新规等因素需要提前弹出游戏自己的权限描述界面 2.玩家连续多次拒绝权限 ...

  5. Qt for Android 动态申请权限

    前言 Qt 随着版本的不断更新,提供了越来越多的接口用于移动端的开发,这里要说的是关于 Android 上权限动态申请的问题,直接在 C++端调用 Qt 的接口即可以实现. 正文 Qt 申请Andro ...

  6. android申请权限一次性申请多个,Android 批量申请权限

    Android开发时,到6.0系统上之后,有的权限就得申请才能用了. Android将权限分为正常权限 和 危险权限 详细可参考: (https://www.cnblogs.com/liuzhipen ...

  7. android 动态申请权限_你真的了解Android权限机制吗?

    码个蛋(codeegg)第 610 次推文 作者:FeelsChaotic 原文:https://www.jianshu.com/p/a17c8bed79d9 前言 Android将安全设计贯穿系统架 ...

  8. android动态申请权限第三方库,Android 关于动态申请权限

    第一种方式:引入三方库,利用第三方申请权限 1.引入三方库: api'com.tbruyelle.rxpermissions2:rxpermissions:0.9.3@aar' 2.调用(当然你需要什 ...

  9. Android动态申请权限(拨打电话)

    Android权限大全: https://www.cnblogs.com/diyishijian/p/5629545.html Android权限有上百个,那么我们如何在调用权限的时候,判断app是否 ...

最新文章

  1. 成为算法工程师的路上,掌握什么思维会让自我提升突飞猛进?
  2. 教你如何防范远程桌面协议(RDP)的安全威胁
  3. oracle解锁system密码,Oracle System密码忘记 密码修改、删除账号锁定lock
  4. 巨人网络开发工程师试题
  5. 综合布线成数据中心建设和运营的重要课题
  6. 【杂谈】AI工业界都有哪些值得参加的比赛?
  7. mybatis.net mysql_ADO.NET与ORM的比较(5):MyBatis实现CRUD
  8. 关于Fiori MyAccount无法在standalone环境下运行的问题
  9. WordPress的用户系统总结
  10. 用URLGather来管理和保存你的页面
  11. 聯想集團與NBA簽署營銷協議
  12. eclipse构建及运行maven web项目
  13. android缩放动画后,Android ObjectAnimator:缩放后动画填充
  14. centos7 无法yum安装mysql_CentOS7 安装mysql(YUM源方式)
  15. 如何在《救赎之路》中使用CPU粒子效果
  16. Mac一体化数据库管理和迁移工具Navicat Premium
  17. 计算机科学管理学专业大学排名,2020管理科学专业大学排名
  18. python shell 下方向键乱码
  19. MATLAB画风速带有方向的矢量图程序,Matlab向量矢量图
  20. Linux命令--arp--使用/实例

热门文章

  1. 4x6矩阵键盘反转法c语言,反转法矩阵键盘
  2. Ubuntu 16.04或14.04里下安装搜狗输入法(图文详解)(全网最简单)
  3. 上班时老是上网闲逛怎么办?幸好我是程序猿!
  4. 分享几个数据库备份脚本
  5. Huffman编码 - 贪心算法
  6. 搭建机器人电控系统——常用电路——输入保护,过压保护,过流保护,光耦隔离抗干扰,输入补偿,稳压
  7. java中使用事务案例_Java事务处理全解析(四)—— 成功的案例(自己实现一个线程安全的TransactionManager)...
  8. 函数项级数的一致收敛
  9. simulink Stateflow使用
  10. VS2008 .ncb工程文件导致调试错误