Android save img to sqlite

本文介绍如何使用Java将Bitmap转换为Blob存储到SQLite数据库,并从数据库中读取Bitmap以显示在ImageView上。重点在于了解如何在数据库操作中处理图像数据,包括使用ContentValues和Blob类型,以及从SQLite查询结果中读取并解码为Bitmap的过程。

1、bitmap保存到SQLite 中 数据格式:    

Java代码  收藏代码
  1. db.execSQL("Create table express ( _id INTEGER PRIMARY KEY AUTOINCREMENT,express_no varchar(100),express_name TEXT,express_img BLOB );");  

 

2、bitmap 变为 Blob   

Java代码  收藏代码
  1. ContentValues values = new ContentValues();  
  2. final ByteArrayOutputStream os = new ByteArrayOutputStream();   
  3. bmp.compress(Bitmap.CompressFormat.PNG, 100, os);    
  4. values.put("express_img", os.toByteArray());  
  5. values.put("express_name","zf");  
  6. values.put("express_no","zf");  
  7. getContentResolver().insert("express", values);  

 

 3、从SQLite中读取Bitmap    

Java代码  收藏代码
  1. byte[] in=cur.getBlob(cur.getColumnIndex("express_img"));  
  2. bmpout=BitmapFactory.decodeByteArray(in,0,in.length);  

 显示在ImageView上

Java代码  收藏代码
  1. ImageView imageView = (ImageView) view.findViewById(R.id.img);   
  2. ByteArrayInputStream stream = new ByteArrayInputStream(cur.getBlob(cur.getColumnIndex("express_img")));   
  3. imageView.setImageDrawable(Drawable.createFromStream(stream, "img"));   

 

总结:

inputStream:  作为数据缓存,数据写如何供别的对象读取,其方法为read();

outputStream:作为数据缓存,将来向别的对象写内容!其方法write();

Java代码  收藏代码
  1. byte[] in=cur.getBlob(cur.getColumnIndex(MyUser.User.BITMAP_VALUES));  

 //这样也可以对数据进行初始化,byte是基本类型,不需要之前进行长度定义。

package com.example.labmemotest; import androidx.appcompat.app.AppCompatActivity; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.widget.ImageView; import android.widget.TextView; import android.widget.LinearLayout; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; import com.bumptech.glide.Glide; import com.example.labmemotest.db.labDbHelper; // 确保路径正确 // 详情页面 public class DetailActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); // 获取传递的数据 Bundle bundle = getIntent().getExtras(); if (bundle != null) { String memo_id = bundle.getString("memo_id"); String title = bundle.getString("title"); String content = bundle.getString("content"); String imgpath = bundle.getString("imgpath"); // 兼容旧逻辑 String time = bundle.getString("time"); String location = bundle.getString("location"); // 显示编号 TextView memo_idTextView = findViewById(R.id.detail_memo_id); memo_idTextView.setText("编号:" + memo_id); // 显示标题 TextView titleTextView = findViewById(R.id.detail_title); titleTextView.setText(title); // 显示地点 TextView locationTextView = findViewById(R.id.detail_location); if (location != null && !location.isEmpty()) { locationTextView.setText("地点:" + location); } else { locationTextView.setText("地点:未设置"); } // 显示内容 TextView contentTextView = findViewById(R.id.detail_content); contentTextView.setText(content); // 显示时间 TextView timeTextView = findViewById(R.id.detail_time); timeTextView.setText(time); // 显示所有图片 LinearLayout imagesContainer = findViewById(R.id.detail_images_container); labDbHelper dbHelper = new labDbHelper(this); SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.query( "tb_image", new String[]{"imgpath"}, "memo_id = ?", new String[]{memo_id}, null, null, null ); if (cursor == null) { Log.e("DetailActivity", "查询图片失败,Cursor 为 null"); return; } try { while (cursor.moveToNext()) { try { String imgPath = cursor.getString(cursor.getColumnIndexOrThrow("imgpath")); Log.d("DetailActivity", "图片路径: " + imgPath); // ✅ 自定义判断是否为合法的 content:// 或 file:// URI Uri uri; try { uri = Uri.parse(imgPath); } catch (Exception e) { Log.e("DetailActivity", "URI 解析失败: " + imgPath, e); continue; } // ✅ 判断协议类型(手动实现替代 UriUtils) boolean isValidUri = isContentUri(uri) || isFileUri(uri); Log.d("DetailActivity", "URI 类型检查: " + (isValidUri ? "有效" : "无效")); ImageView imageView = new ImageView(this); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, 600 ); params.bottomMargin = 16; imageView.setLayoutParams(params); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setBackgroundColor(Color.parseColor("#F5F5F5")); // 使用 Glide 加载图片,带占位图和错误图 Glide.with(this) .load(uri) .placeholder(R.drawable.ic_default_image) .error(R.drawable.ic_error_image) .into(imageView); imagesContainer.addView(imageView); } catch (Exception e) { Log.e("DetailActivity", "处理单个图片时出错", e); addErrorImage(imagesContainer); // 添加错误提示图 } } } finally { cursor.close(); db.close(); } } } // ✅ 手动实现:判断是否为 content:// URI private boolean isContentUri(Uri uri) { return "content".equals(uri.getScheme()); } // ✅ 手动实现:判断是否为 file:// URI private boolean isFileUri(Uri uri) { return "file".equals(uri.getScheme()); } // ✅ 辅助方法:添加错误图片占位符 private void addErrorImage(LinearLayout container) { ImageView errorView = new ImageView(this); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, 600 ); params.bottomMargin = 16; errorView.setLayoutParams(params); errorView.setScaleType(ImageView.ScaleType.CENTER); errorView.setBackgroundColor(Color.parseColor("#FFEBEE")); // 浅红背景 errorView.setImageResource(R.drawable.ic_error_image); // 替换为你自己的图标 container.addView(errorView); } } package com.example.labmemotest.bean; public class MemoBean { private String memoId; // 9位随机编号(memo_id) private String title; // 备忘录标题 private String content; // 备忘录内容 private String location; // 备忘录地点 private String time; // 时间 // 构造方法(移除imgpath参数) public MemoBean(String title, String content, String time, String location, String memoId) { this.title = title; this.content = content; this.time = time; this.location = location; this.memoId = memoId; } // Getter和Setter方法(移除imgpath相关) public String getMemoId() { return memoId; } public void setMemoId(String memoId) { this.memoId = memoId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } }package com.example.labmemotest.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; //数据库 public class labDbHelper extends SQLiteOpenHelper { // 定义数据库名、版本号 private static final String DBNAME = “zsmemo.db”; private static final int VERSION = 3; // 版本号从2升级到3,支持多图存储 private static final String TAG = “labDbHelper”; public labDbHelper(Context context) { super(context, DBNAME, null, VERSION); } // 创建数据库(仅首次安装时执行) @Override public void onCreate(SQLiteDatabase db) { // 启用外键约束(关键:SQLite默认关闭外键约束) db.execSQL("PRAGMA foreign_keys = ON;"); // 1. 日志表(移除imgpath字段) String createMemoryTable = "CREATE TABLE tb_memory (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + "memo_id TEXT unique," + "title TEXT, " + "content TEXT, " + "location TEXT," + "mtime TEXT)"; // 已移除原imgpath字段 // 2. 新增图片表(存储多图,关联日志表的memo_id) String createImageTable = "CREATE TABLE tb_image (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + "memo_id TEXT, " + "imgpath TEXT, " + "FOREIGN KEY(memo_id) REFERENCES tb_memory(memo_id))"; try { db.execSQL(createMemoryTable); db.execSQL(createImageTable); Log.d(TAG, "日志表和图片表创建成功"); } catch (Exception e) { Log.e(TAG, "创建表失败:" + e.getMessage(), e); // 移除强制崩溃的代码,改为友好处理 Log.e(TAG, "数据库初始化失败,应用可能无法正常工作", e); } } // 升级数据库(版本号提升时执行) @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 升级时启用外键约束 db.execSQL("PRAGMA foreign_keys = ON;"); // 处理版本2升级到3的情况(支持多图存储) if (oldVersion < 3) { try { // 兼容低版本SQLite,使用临时表迁移方式移除imgpath字段 // 1. 创建临时表(不含imgpath字段) db.execSQL("CREATE TABLE tb_memory_temp (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + "memo_id TEXT unique," + "title TEXT, " + "content TEXT, " + "location TEXT," + "mtime TEXT)"); // 2. 迁移旧表数据(排除imgpath字段) db.execSQL("INSERT INTO tb_memory_temp " + "(_id, memo_id, title, content, location, mtime) " + "SELECT _id, memo_id, title, content, location, mtime FROM tb_memory"); // 3. 删除旧表 db.execSQL("DROP TABLE tb_memory"); // 4. 重命名临时表为正式表 db.execSQL("ALTER TABLE tb_memory_temp RENAME TO tb_memory"); // 5. 创建图片表 db.execSQL("CREATE TABLE tb_image (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + "memo_id TEXT, " + "imgpath TEXT, " + "FOREIGN KEY(memo_id) REFERENCES tb_memory(memo_id))"); Log.d(TAG, "数据库升级到版本3,支持多图存储"); } catch (Exception e) { Log.e(TAG, "数据库升级失败:" + e.getMessage(), e); } } } } package com.example.labmemotest.adapter; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.ScaleAnimation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.example.labmemotest.DetailActivity; import com.example.labmemotest.R; import com.example.labmemotest.bean.MemoBean; import com.example.labmemotest.db.labDbHelper; import java.util.List; import java.util.Random; public class MemoAdapter extends RecyclerView.Adapter<MemoAdapter.ViewHolder> { private Context mcontext; private List arr1; // 移除类级别的数据库变量,改为在使用时局部声明 private static final int MAX_TITLE_LENGTH = 10; private static final int MAX_CONTENT_LENGTH = 8; private static final int COLOR_MIN = 180; private static final int COLOR_MAX = 255; public MemoAdapter(Context mcontext, List<MemoBean> arr1) { this.mcontext = mcontext; this.arr1 = arr1; } @NonNull @Override public MemoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(mcontext).inflate(R.layout.recy_item, parent, false); return new ViewHolder(view); } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) @Override public void onBindViewHolder(@NonNull MemoAdapter.ViewHolder holder, int position) { // 增加空数据判断,避免数组越界 if (arr1 == null || position >= arr1.size()) { return; } final MemoBean memoBean = arr1.get(position); // 防止memoBean为空导致的空指针 if (memoBean == null) { return; } // 显示编号(增加非空判断) holder.item_memo_id.setText("编号:" + (memoBean.getMemoId() != null ? memoBean.getMemoId() : "未知")); // 处理标题长度 String title = memoBean.getTitle(); if (title == null) title = ""; // 防止空指针 if (title.length() > MAX_TITLE_LENGTH) { title = title.substring(0, MAX_TITLE_LENGTH) + "..."; } holder.item_title.setText(title); // 处理内容显示 String content = memoBean.getContent(); if (content == null) content = ""; // 防止空指针 if (content.length() > MAX_CONTENT_LENGTH) { content = content.substring(0, MAX_CONTENT_LENGTH) + "..."; } holder.item_content.setText(content); // 处理时间显示 String time = memoBean.getTime(); holder.item_time.setText(time != null ? time : ""); // 处理位置信息 String location = memoBean.getLocation(); if (location != null && !location.isEmpty()) { holder.item_location.setText("位置:" + location); } else { holder.item_location.setText("地点:无"); } holder.item_location.setVisibility(View.VISIBLE); // 从tb_image表查询对应memo_id的第一张图片 // 使用局部变量处理数据库操作,避免多线程问题 labDbHelper dbHelper = new labDbHelper(mcontext); SQLiteDatabase db = null; Cursor cursor = null; try { db = dbHelper.getReadableDatabase(); // 确保memoId不为空 if (memoBean.getMemoId() != null) { cursor = db.query( "tb_image", new String[]{"imgpath"}, "memo_id = ?", new String[]{memoBean.getMemoId()}, null, null, null, "1" ); if (cursor != null && cursor.moveToFirst()) { String firstImgPath = cursor.getString(0); if (firstImgPath != null && !firstImgPath.isEmpty()) { // 加载图片 Glide.with(mcontext) .load(Uri.parse(firstImgPath)) .error(R.drawable.ic_default_image) .placeholder(R.drawable.ic_default_image) // 新增占位图 .into(holder.item_img); } else { holder.item_img.setImageResource(R.drawable.ic_default_image); } } else { holder.item_img.setImageResource(R.drawable.ic_default_image); } } else { holder.item_img.setImageResource(R.drawable.ic_default_image); } } catch (Exception e) { Log.e("MemoAdapter", "图片加载失败", e); holder.item_img.setImageResource(R.drawable.ic_default_image); } finally { // 确保资源关闭 if (cursor != null) { try { cursor.close(); } catch (Exception e) { Log.e("MemoAdapter", "关闭cursor失败", e); } } if (db != null && db.isOpen()) { try { db.close(); } catch (Exception e) { Log.e("MemoAdapter", "关闭数据库失败", e); } } } // 设置背景颜色和形状 Random random = new Random(); int red = COLOR_MIN + random.nextInt(COLOR_MAX - COLOR_MIN); int green = COLOR_MIN + random.nextInt(COLOR_MAX - COLOR_MIN); int blue = COLOR_MIN + random.nextInt(COLOR_MAX - COLOR_MIN); int color = Color.argb(200, red, green, blue); GradientDrawable gradientDrawable = new GradientDrawable(); gradientDrawable.setShape(GradientDrawable.RECTANGLE); gradientDrawable.setCornerRadius(10f); gradientDrawable.setColor(color); gradientDrawable.setStroke(2, Color.argb(50, 0, 0, 0)); holder.item_layout.setBackground(gradientDrawable); // 子项点击事件 - 跳转到详情页 holder.item_layout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ScaleAnimation scaleAnim = new ScaleAnimation( 1.0f, 0.95f, 1.0f, 0.95f, v.getWidth() / 2, v.getHeight() / 2); scaleAnim.setDuration(100); v.startAnimation(scaleAnim); Intent intent = new Intent(mcontext, DetailActivity.class); Bundle bundle = new Bundle(); bundle.putString("title", memoBean.getTitle()); bundle.putString("content", memoBean.getContent()); bundle.putString("time", memoBean.getTime()); bundle.putString("memo_id", memoBean.getMemoId()); bundle.putString("location", memoBean.getLocation()); intent.putExtras(bundle); mcontext.startActivity(intent); } }); // 长按事件 - 删除功能 holder.item_layout.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { new AlertDialog.Builder(mcontext) .setMessage("确定删除吗?") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int which) { // 使用局部变量处理数据库 labDbHelper deleteHelper = new labDbHelper(mcontext); SQLiteDatabase deleteDb = null; try { deleteDb = deleteHelper.getWritableDatabase(); int pos = holder.getAdapterPosition(); if (pos != RecyclerView.NO_POSITION && arr1.get(pos) != null && arr1.get(pos).getMemoId() != null) { // 删除主表记录 deleteDb.delete("tb_memory", "memo_id = ?", new String[]{arr1.get(pos).getMemoId()}); // 删除关联图片 deleteDb.delete("tb_image", "memo_id = ?", new String[]{arr1.get(pos).getMemoId()}); // 更新列表 arr1.remove(pos); notifyItemRemoved(pos); notifyItemRangeChanged(pos, arr1.size()); } } catch (Exception e) { Log.e("MemoAdapter", "删除失败", e); } finally { if (deleteDb != null && deleteDb.isOpen()) { try { deleteDb.close(); } catch (Exception e) { Log.e("MemoAdapter", "关闭数据库失败", e); } } } dialogInterface.dismiss(); } }) .setNegativeButton("取消", null) .setCancelable(false) .show(); return true; } }); } @Override public int getItemCount() { return arr1 == null ? 0 : arr1.size(); // 防止空指针 } public boolean isEmpty() { return arr1 == null || arr1.isEmpty(); } public class ViewHolder extends RecyclerView.ViewHolder { TextView item_title, item_content, item_time, item_memo_id, item_location; ImageView item_img; LinearLayout item_layout; public ViewHolder(@NonNull View itemView) { super(itemView); // 增加控件绑定的空判断 item_memo_id = itemView.findViewById(R.id.item_memo_id); item_title = itemView.findViewById(R.id.item_title); item_content = itemView.findViewById(R.id.item_content); item_img = itemView.findViewById(R.id.item_image); item_location = itemView.findViewById(R.id.item_location); item_time = itemView.findViewById(R.id.item_time); item_layout = itemView.findViewById(R.id.item_layout); } } } package com.example.labmemotest; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.SearchView; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.StaggeredGridLayoutManager; import com.example.labmemotest.bean.MemoBean; import com.example.labmemotest.adapter.MemoAdapter; import com.example.labmemotest.db.labDbHelper; import java.util.ArrayList; import java.util.List; //主页面 public class MainActivity extends AppCompatActivity { // 定义对象 private Button btn_add; private RecyclerView recy_view; private SearchView searchView; // 新增:搜索框 // 添加数据库相关对象 private labDbHelper mhelper; SQLiteDatabase db; // 新增:用于存储所有数据和过滤后的数据 private List<MemoBean> allMemos = new ArrayList<>(); private MemoAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 修改:使用包含搜索框的布局文件 setContentView(R.layout.activity_main); // 1.绑定控件 initView(); // 2.对按钮添加单击事件 btnonclicknext(); // 3.添加从数据库获取数据并显示到RecyclerView的功能 recyDisplay(); // 新增:设置搜索监听 setupSearchListener(); } // 1.绑定控件 private void initView() { btn_add = findViewById(R.id.button_add); recy_view = findViewById(R.id.recy_view); // 新增:绑定搜索框 searchView = findViewById(R.id.search_view); recy_view.setLayoutManager(new LinearLayoutManager(this)); // 初始化数据库帮助类和数据库对象 mhelper = new labDbHelper(MainActivity.this); db = mhelper.getWritableDatabase(); } // 2.对按钮添加单击事件 private void btnonclicknext() { btn_add.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 单击后跳转到下一页 Intent intent = new Intent(MainActivity.this, AddInfoActivity.class); startActivity(intent); finish(); } }); } // 3.从数据库获取数据,显示到RecyclerView 控件中 private void recyDisplay() { // 准备数据集合 allMemos.clear(); // 清空原有数据 // 从数据库中查询数据 Cursor cursor = db.rawQuery("select * from tb_memory", null); while (cursor.moveToNext()) { String mytitle = cursor.getString(cursor.getColumnIndex("title")); String mycontent = cursor.getString(cursor.getColumnIndex("content")); // String myimgpath = cursor.getString(cursor.getColumnIndex("imgpath")); String mymtime = cursor.getString(cursor.getColumnIndex("mtime")); String mylocation = cursor.getString(cursor.getColumnIndex("location")); String mymemoId = cursor.getString(cursor.getColumnIndex("memo_id")); MemoBean memoBean = new MemoBean(mytitle, mycontent, mymtime, mylocation, mymemoId); allMemos.add(memoBean); } cursor.close(); // 关闭游标 // 创建并设置适配器 adapter = new MemoAdapter(MainActivity.this, new ArrayList<>(allMemos)); // 保留原有的布局管理器设置 StaggeredGridLayoutManager st = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); recy_view.setLayoutManager(st); recy_view.setAdapter(adapter); } // 新增:设置搜索监听 private void setupSearchListener() { // 设置搜索提示文字 searchView.setQueryHint("搜索编号..."); // 设置默认不折叠 searchView.setIconifiedByDefault(false); // 搜索文本变化监听 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { filterMemos(query); return true; } @Override public boolean onQueryTextChange(String newText) { filterMemos(newText); return true; } }); } // 新增:根据搜索内容过滤数据 private void filterMemos(String query) { List<MemoBean> filteredList = new ArrayList<>(); if (query == null || query.trim().isEmpty()) { // 如果搜索为空,显示所有数据 filteredList.addAll(allMemos); } else { // 否则过滤出编号包含搜索内容的项 String lowerCaseQuery = query.toLowerCase().trim(); for (MemoBean memo : allMemos) { if (memo.getMemoId() != null && memo.getMemoId().contains(lowerCaseQuery)) { filteredList.add(memo); } } } // 更新适配器数据 adapter = new MemoAdapter(MainActivity.this, filteredList); recy_view.setAdapter(adapter); } } package com.example.labmemotest; import androidx.appcompat.app.AppCompatActivity; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import android.Manifest; import android.content.ContentValues; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; import com.blankj.utilcode.util.UriUtils; import com.bumptech.glide.Glide; import com.example.labmemotest.db.labDbHelper; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; //日志创建页面 public class AddInfoActivity extends AppCompatActivity { private EditText edit_title, edit_content, edit_location; private Button btn_camera, btn_photo, btn_save; private LinearLayout previewContainer; private String tmp_path; private labDbHelper mhelper; private SQLiteDatabase db; private List imagePaths = new ArrayList<>(); // 存储图片Uri字符串 private Handler handler = new Handler(Looper.getMainLooper()); // 添加Handler private ActivityResultLauncher<Intent> cameraLauncher; private ActivityResultLauncher<Intent> galleryLauncher; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_add_info); initLaunchers(); initView(); initDb(); // 确保数据库先初始化 btnOnClick(); } private void initLaunchers() { // 相机返回结果处理 cameraLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { // 相机图片用FileProvider生成的Uri存储 Uri imageUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", new File(tmp_path)); imagePaths.add(imageUri.toString()); // 存储Uri字符串 updateImagePreviews(); } } ); // 相册返回结果处理 galleryLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK && result.getData() != null) { Intent data = result.getData(); try { if (data.getClipData() != null) { int count = data.getClipData().getItemCount(); for (int i = 0; i < count; i++) { Uri imageUri = data.getClipData().getItemAt(i).getUri(); imagePaths.add(imageUri.toString()); // 直接存储Uri } } else if (data.getData() != null) { Uri imageUri = data.getData(); imagePaths.add(imageUri.toString()); } updateImagePreviews(); } catch (Exception e) { Log.e("GalleryError", "相册选择失败", e); Toast.makeText(this, "图片选择失败", Toast.LENGTH_SHORT).show(); } } } ); } private void initView() { edit_title = findViewById(R.id.editText_title); edit_content = findViewById(R.id.editText_content); edit_location = findViewById(R.id.editText_location); btn_camera = findViewById(R.id.button_camera); btn_photo = findViewById(R.id.button_photo); btn_save = findViewById(R.id.button_save); previewContainer = findViewById(R.id.image_preview_container); } private void initDb() { try { mhelper = new labDbHelper(AddInfoActivity.this); db = mhelper.getWritableDatabase(); } catch (Exception e) { Log.e("DbInitError", "数据库初始化失败", e); Toast.makeText(this, "数据库初始化失败", Toast.LENGTH_SHORT).show(); } } private void btnOnClick() { btn_camera.setOnClickListener(v -> checkCameraPermission()); btn_photo.setOnClickListener(v -> checkGalleryPermission()); btn_save.setOnClickListener(v -> btnSave()); } // 检查相机权限(适配Android 13+) private void checkCameraPermission() { List<String> permissions = new ArrayList<>(); if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { permissions.add(Manifest.permission.CAMERA); } // Android 13+ 不需要WRITE_EXTERNAL_STORAGE,用分区存储 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } if (!permissions.isEmpty()) { ActivityCompat.requestPermissions(this, permissions.toArray(new String[0]), 100); } else { openCamera(); } } // 检查相册权限(适配Android 13+) private void checkGalleryPermission() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, 200); } else { openGallery(); } } else { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 200); } else { openGallery(); } } } private void openCamera() { try { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String randtime = sdf.format(date); File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File imgfile = new File(storageDir, "image" + randtime + ".jpg"); tmp_path = imgfile.getAbsolutePath(); Uri photoURI = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", imgfile); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); cameraLauncher.launch(intent); } catch (Exception e) { Log.e("CameraError", "相机打开失败", e); Toast.makeText(this, "无法打开相机", Toast.LENGTH_SHORT).show(); } } private void openGallery() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); galleryLauncher.launch(intent); } private void updateImagePreviews() { previewContainer.removeAllViews(); for (String uriString : imagePaths) { try { ImageView imageView = new ImageView(this); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 300, 300, 1.0f ); params.setMargins(8, 8, 8, 8); imageView.setLayoutParams(params); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setBackgroundColor(Color.parseColor("#F5F5F5")); Glide.with(this).load(Uri.parse(uriString)).into(imageView); // 直接加载Uri previewContainer.addView(imageView); } catch (Exception e) { Log.e("PreviewError", "图片预览失败", e); } } } // 生成唯一ID(添加数据库操作异常捕获) private String generateUniqueMemoId() { if (db == null || !db.isOpen()) { initDb(); // 重新初始化数据库 } Random random = new Random(); String memoId; while (true) { long randomNum = 100000000L + random.nextLong() % 900000000L; if (randomNum < 0) randomNum = -randomNum; memoId = String.valueOf(randomNum); try (Cursor cursor = db.query( // 自动关闭cursor "tb_memory", new String[]{"memo_id"}, "memo_id = ?", new String[]{memoId}, null, null, null )) { if (!cursor.moveToNext()) { break; } } catch (Exception e) { Log.e("GenerateIdError", "ID生成失败", e); Toast.makeText(this, "编号生成失败", Toast.LENGTH_SHORT).show(); return ""; // 异常时返回空 } } return memoId; } private void btnSave() { String title = edit_title.getText().toString().trim(); String content = edit_content.getText().toString().trim(); String location = edit_location.getText().toString().trim(); if (title.isEmpty()) { Toast.makeText(this, "请输入标题", Toast.LENGTH_SHORT).show(); return; } if (db == null || !db.isOpen()) { Toast.makeText(this, "数据库未初始化", Toast.LENGTH_SHORT).show(); return; } String uniqueMemoId = generateUniqueMemoId(); if (uniqueMemoId.isEmpty()) { return; // ID生成失败直接返回 } try { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); String mtime = sdf.format(date); ContentValues memoryValues = new ContentValues(); memoryValues.put("memo_id", uniqueMemoId); memoryValues.put("title", title); memoryValues.put("content", content); memoryValues.put("location", location); memoryValues.put("mtime", mtime); long insertResult = db.insert("tb_memory", null, memoryValues); if (insertResult != -1) { // 批量插入图片 for (String uriString : imagePaths) { ContentValues imageValues = new ContentValues(); imageValues.put("memo_id", uniqueMemoId); imageValues.put("imgpath", uriString); // 存储Uri字符串 db.insert("tb_image", null, imageValues); } // 关闭数据库 if (db != null && db.isOpen()) { db.close(); } Toast.makeText(this, "保存成功(" + imagePaths.size() + "张图片)", Toast.LENGTH_SHORT).show(); // 使用Handler延迟启动主活动,避免数据库操作冲突 handler.postDelayed(() -> { startActivity(new Intent(AddInfoActivity.this, MainActivity.class)); finish(); }, 500); } else { Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { Log.e("SaveError", "保存失败", e); Toast.makeText(this, "保存失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openCamera(); } else { Toast.makeText(this, "需要相机权限才能拍照", Toast.LENGTH_SHORT).show(); } } else if (requestCode == 200) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openGallery(); } else { Toast.makeText(this, "需要相册权限才能选择图片", Toast.LENGTH_SHORT).show(); } } } @Override public void onBackPressed() { // 关闭数据库 if (db != null && db.isOpen()) { db.close(); } startActivity(new Intent(AddInfoActivity.this, MainActivity.class)); finish(); } @Override protected void onDestroy() { super.onDestroy(); // 确保数据库已关闭 if (db != null && db.isOpen()) { try { db.close(); } catch (Exception e) { Log.e("DestroyError", "关闭数据库失败", e); } } } }阅读以上多个代码,分析在子项控件详情页面里图片无法正常显示的问题,以及日志创建页面提取权限后依然无法拍照的问题,分析解决并给出具体解决方案和代码
最新发布
09-28
<think>我们面临两个主要问题: 1. 在子项控件详情页面(DetailActivity)里图片无法正常显示。 2. 在日志创建页面(AddInfoActivity)提取权限后依然无法拍照。 首先,我们逐一分析问题并给出解决方案。 ### 问题1:DetailActivity中图片无法正常显示 在DetailActivity中,我们通过Glide加载图片使用的是从数据库中读取的URI字符串,然后解析成Uri对象。但是,我们需要注意以下几点: - 图片路径在保存时是以Uri字符串形式存储的(例如:`content://...` 或 `file://...`)。 - 在DetailActivity中,我们使用`Uri.parse()`将字符串转换成Uri对象,然后用Glide加载。 但是,问题可能出在: - 存储的URI字符串可能不是有效的URI(比如是文件路径而不是URI)。 - 对于`content://`类型的URI,需要确保有读取权限(特别是在Android 10及以上版本)。 在代码中,我们已经在DetailActivity中实现了URI解析和类型检查,并且Glide本身支持加载content URI。因此,问题可能在于权限。 解决方案: - 对于`content://` URI,我们需要在查询图片时使用`ContentResolver`并确保有读取权限。但是,由于我们在DetailActivity中是从数据库读取的URI字符串,并且这些URI是在添加时通过相册选择或相机拍摄得到的,所以我们应该已经拥有了这些URI的读取权限(因为在选择图片或拍照时系统会授予临时权限)。但是,临时权限在Activity结束后可能会失效,所以我们需要持久化权限。 如何持久化权限? - 使用`ContentResolver`的`takePersistableUriPermission()`方法,在获取到URI后立即获取持久化权限(需要API 19+)。 - 在AddInfoActivity中,当我们从相册选择图片或拍照后,我们应该获取持久化权限。但是,相机拍摄的图片我们是通过FileProvider提供的,是app私有目录,不需要权限;而相册选择的图片是共享存储,需要持久化权限。 修改AddInfoActivity: 在相册选择图片后,对每个选中的URI获取持久化读取权限。 ### 问题2:AddInfoActivity中提取权限后依然无法拍照 在`checkCameraPermission()`方法中,我们请求了相机权限和(低于Android 13的)存储权限。但是,在权限授予后,我们调用了`openCamera()`方法。问题可能出现在: - 临时文件路径`tmp_path`没有正确设置,导致相机应用无法写入。 - FileProvider的配置不正确,导致无法生成有效的URI。 在`openCamera()`方法中,我们使用了FileProvider来生成URI,并且将图片保存到app的私有目录(通过`getExternalFilesDir`)。这应该是正确的。 但是,在Android 7.0以上,我们无法通过`file://`URI共享文件,必须使用FileProvider。我们已经在`AndroidManifest.xml`中配置了FileProvider,并且使用了正确的authority。 检查FileProvider配置: - 在`AndroidManifest.xml`中,我们需要配置FileProvider,并且确保`authorities`的值与代码中使用的authority一致(即`getPackageName() + ".fileprovider"`)。 另外,在调用相机时,我们设置了`Intent.FLAG_GRANT_READ_URI_PERMISSION`,这应该会授予相机应用读取我们提供的URI的权限。 然而,问题可能在于:我们在`cameraLauncher`中启动相机,但是当相机应用返回时,我们使用`tmp_path`来创建URI,这个`tmp_path`是在启动相机前创建的。但是,相机应用是否真的将图片保存到了这个位置?我们可以通过检查文件是否存在来确认。 解决方案: - 在`cameraLauncher`的结果处理中,检查文件是否存在。如果不存在,可能是相机应用没有保存成功,或者保存的位置不对。 ### 具体代码修改 #### 问题1:DetailActivity图片显示问题(权限持久化) 修改AddInfoActivity的相册选择部分,在获取到相册的URI后,获取持久化权限。 在`galleryLauncher`的结果处理中,添加如下代码: ```java // 在相册选择后 try { if (data.getClipData() != null) { // ... 多选 } else if (data.getData() != null) { // ... 单选 } // 获取持久化权限 for (String uriString : imagePaths) { Uri uri = Uri.parse(uriString); // 获取持久化读取权限 getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } catch (Exception e) { Log.e("PermissionError", "获取持久化权限失败", e); } ``` 注意:`takePersistableUriPermission`方法只在API>=19可用,并且要求URI是通过`Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION`授予的。但是,我们在选择图片时,使用的是`ACTION_GET_CONTENT`,系统并未授予持久化权限。因此,我们需要改用`ACTION_OPEN_DOCUMENT`来获取持久化权限。 所以,我们需要修改打开相册的Intent: ```java private void openGallery() { // 使用ACTION_OPEN_DOCUMENT以获取持久化权限 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.setType("image/*"); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // 持久化 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 可读 galleryLauncher.launch(intent); } ``` 这样,在选择图片后,我们就可以获取持久化权限,即使在应用重启后也能读取这些图片。 #### 问题2:拍照问题 在`openCamera()`方法中,我们创建了一个临时文件,并将路径保存在`tmp_path`中,然后通过FileProvider生成URI传递给相机应用。问题可能在于相机应用没有成功保存图片,或者保存后我们无法读取。 在结果回调中,我们直接使用`tmp_path`来生成URI。但实际上,相机应用应该已经将图片保存到了我们指定的位置。我们可以检查文件是否存在。 修改`cameraLauncher`的结果处理: ```java cameraLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { File imgFile = new File(tmp_path); if (imgFile.exists()) { Uri imageUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", imgFile); imagePaths.add(imageUri.toString()); updateImagePreviews(); } else { Toast.makeText(this, "图片文件不存在", Toast.LENGTH_SHORT).show(); } } } ); ``` 另外,确保FileProvider配置正确。在`AndroidManifest.xml`中: ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` 在`res/xml/file_paths.xml`中,确保包含了我们保存图片的目录: ```xml <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_images" path="Pictures" /> <!-- 如果需要,还可以添加其他路径 --> </paths> ``` ### 其他可能的问题 1. **DetailActivity中Glide加载图片失败**:除了权限问题,还可能是图片路径不正确。在DetailActivity中添加详细的日志,输出URI字符串和解析后的Uri,以及Glide加载失败的原因(使用Glide的`.listener()`)。 2. **在AddInfoActivity中,保存日志后跳转回MainActivity,但DetailActivity中的图片是之后才显示的,可能因为数据库操作延迟?** 我们在保存后关闭了数据库,并且延迟了500ms跳转,应该没有问题。 ### 修改后的代码片段 #### AddInfoActivity中修改打开相册的方式和获取持久化权限 ```java private void openGallery() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.setType("image/*"); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); galleryLauncher.launch(intent); } // 在galleryLauncher的结果处理中,添加获取持久化权限 if (result.getResultCode() == RESULT_OK && result.getData() != null) { Intent data = result.getData(); try { if (data.getClipData() != null) { // ... 处理多选 } else if (data.getData() != null) { // ... 处理单选 } // 获取持久化权限 for (String uriString : imagePaths) { Uri uri = Uri.parse(uriString); try { final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION); getContentResolver().takePersistableUriPermission(uri, takeFlags); } catch (SecurityException e) { Log.e("PermissionError", "无法获取持久化权限: " + uri, e); } } updateImagePreviews(); } catch (Exception e) { Log.e("GalleryError", "相册选择失败", e); Toast.makeText(this, "图片选择失败", Toast.LENGTH_SHORT).show(); } } ``` #### 相机结果处理中添加文件存在性检查 ```java cameraLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { File imgFile = new File(tmp_path); if (imgFile.exists()) { try { Uri imageUri = FileProvider.getUriForFile(AddInfoActivity.this, getPackageName() + ".fileprovider", imgFile); imagePaths.add(imageUri.toString()); updateImagePreviews(); } catch (Exception e) { Log.e("CameraResult", "生成URI失败", e); Toast.makeText(AddInfoActivity.this, "图片处理失败", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(AddInfoActivity.this, "图片文件不存在,路径: " + tmp_path, Toast.LENGTH_LONG).show(); } } } ); ``` ### DetailActivity中Glide加载添加错误监听 为了便于调试,可以在Glide加载时添加监听器,打印错误日志: ```java Glide.with(this) .load(uri) .placeholder(R.drawable.ic_default_image) .error(R.drawable.ic_error_image) .listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { Log.e("GlideError", "加载失败: " + model, e); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { return false; } }) .into(imageView); ``` ### 总结 通过以上修改,应该能够解决两个问题: 1. 相册图片在DetailActivity中无法显示:通过使用`ACTION_OPEN_DOCUMENT`并获取持久化权限,确保后续可以读取。 2. 拍照后图片不存在:检查文件是否存在,并确保FileProvider配置正确。 最后,请务必检查`AndroidManifest.xml`中的FileProvider配置和权限声明。 §§相关问题§§: 1. 在Android 10以上,如何安全地访问共享存储中的媒体文件? 2. Glide加载图片出现错误时,有哪些常见的排查步骤? 3. FileProvider在Android应用中的作用是什么?如何配置? 4. 如何处理应用卸载后通过FileProvider保存的文件? </think>### 问题分析与解决方案 #### 问题1:详情页面图片无法显示 **原因分析:** 1. **URI权限问题**:Android 10+ 的 Scoped Storage 限制了直接文件路径访问,使用 `content://` URI 时需要持久化权限 2. **URI格式问题**:图片存储在数据库中可能是文件路径(`/storage/emulated/0/...`)而不是有效的 `content://` URI 3. **权限未持久化**:添加图片时获取的临时URI权限在返回详情页面时已失效 4. **Glide加载问题**:加载 `file://` URI 在 Android 7.0+ 需要 FileProvider **解决方案:** 1. **修改数据库存储方式**:确保始终存储 `content://` URI 2. **添加权限持久化处理** 3. **统一使用 FileProvider 处理文件 URI** 4. **增强Glide的错误处理** ```java // DetailActivity.java 修改部分 private void loadImages(String memo_id) { // ... [省略其他代码] while (cursor.moveToNext()) { String imgPath = cursor.getString(cursor.getColumnIndexOrThrow("imgpath")); Uri uri; try { // 转换文件路径为 content URI if (imgPath.startsWith("/")) { File file = new File(imgPath); uri = FileProvider.getUriForFile( this, getPackageName() + ".fileprovider", file ); // 更新数据库中的路径为标准URI ContentValues values = new ContentValues(); values.put("imgpath", uri.toString()); db.update("tb_image", values, "imgpath = ?", new String[]{imgPath}); } else { uri = Uri.parse(imgPath); } // 确保有读取权限 grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); // 使用Glide加载图片 Glide.with(this) .load(uri) .placeholder(R.drawable.ic_default_image) .error(R.drawable.ic_error_image) .listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { Log.e("GlideError", "加载失败: " + model, e); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { return false; } }) .into(imageView); } catch (Exception e) { Log.e("ImageLoad", "图片加载错误", e); addErrorImage(imagesContainer); } } // ... [省略其他代码] } ``` #### 问题2:拍照权限问题 **原因分析:** 1. **权限请求逻辑问题**:没有正确处理多权限请求的结果 2. **Android 13+ 权限变化**:READ_MEDIA_IMAGES 需要单独处理 3. **FileProvider 配置问题**:可能路径配置不正确 4. **相机结果处理问题**:保存路径可能无效 **解决方案:** ```java // AddInfoActivity.java 修改部分 // 1. 修改权限请求方法 private void checkCameraPermission() { List<String> permissionsNeeded = new ArrayList<>(); // 相机权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.CAMERA); } // Android 13+ 媒体权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.READ_MEDIA_IMAGES); } } // 旧版本存储权限 else if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } if (!permissionsNeeded.isEmpty()) { ActivityCompat.requestPermissions( this, permissionsNeeded.toArray(new String[0]), CAMERA_PERMISSION_CODE ); } else { openCamera(); } } // 2. 修改权限结果处理 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == CAMERA_PERMISSION_CODE) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { openCamera(); } else { Toast.makeText(this, "需要所有权限才能使用相机", Toast.LENGTH_SHORT).show(); } } // ... [其他权限处理] } // 3. 修改相机打开方法 private void openCamera() { try { // 确保目录存在 File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); if (!storageDir.exists()) { storageDir.mkdirs(); } // 创建临时文件 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File imageFile = File.createTempFile( imageFileName, ".jpg", storageDir ); tmp_path = imageFile.getAbsolutePath(); Uri photoURI = FileProvider.getUriForFile( this, getPackageName() + ".fileprovider", imageFile ); // 启动相机 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); cameraLauncher.launch(intent); } catch (IOException e) { Log.e("CameraError", "创建临时文件失败", e); Toast.makeText(this, "无法创建图片文件", Toast.LENGTH_SHORT).show(); } catch (ActivityNotFoundException e) { Toast.makeText(this, "未找到相机应用", Toast.LENGTH_SHORT).show(); } } // 4. 修改文件路径配置 (res/xml/file_paths.xml) <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 添加所有可能的存储路径 --> <external-path name="external_storage" path="." /> <external-files-path name="external_files" path="." /> <files-path name="files" path="." /> <cache-path name="cache" path="." /> </paths> ``` ### 完整修改后的关键类 #### DetailActivity.java (关键修改部分) ```java // 在显示图片的循环中添加以下逻辑 while (cursor.moveToNext()) { try { String imgPath = cursor.getString(cursor.getColumnIndexOrThrow("imgpath")); Uri uri; // 转换文件路径为content URI if (imgPath.startsWith("/")) { File file = new File(imgPath); if (file.exists()) { uri = FileProvider.getUriForFile( this, getPackageName() + ".fileprovider", file ); // 更新数据库中的路径 ContentValues values = new ContentValues(); values.put("imgpath", uri.toString()); db.update("tb_image", values, "imgpath = ?", new String[]{imgPath}); } else { throw new FileNotFoundException("文件不存在: " + imgPath); } } else { uri = Uri.parse(imgPath); } // 确保有读取权限 grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); getContentResolver().takePersistableUriPermission( uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); // 使用Glide加载 Glide.with(this) .load(uri) .placeholder(R.drawable.ic_default_image) .error(R.drawable.ic_error_image) .into(imageView); } catch (Exception e) { Log.e("ImageLoad", "图片加载错误: " + imgPath, e); addErrorImage(imagesContainer); } } ``` #### AddInfoActivity.java (关键修改部分) ```java // 修改权限检查和处理逻辑 private static final int CAMERA_PERMISSION_CODE = 100; private void checkCameraPermission() { List<String> permissions = new ArrayList<>(); // 总是需要相机权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { permissions.add(Manifest.permission.CAMERA); } // Android 13+ 需要READ_MEDIA_IMAGES if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { permissions.add(Manifest.permission.READ_MEDIA_IMAGES); } } // Android 10-12 需要WRITE_EXTERNAL_STORAGE else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } } if (permissions.isEmpty()) { openCamera(); } else { ActivityCompat.requestPermissions( this, permissions.toArray(new String[0]), CAMERA_PERMISSION_CODE ); } } // 修改相机结果处理 cameraLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { File imgFile = new File(tmp_path); if (imgFile.exists()) { Uri imageUri = FileProvider.getUriForFile( this, getPackageName() + ".fileprovider", imgFile ); // 获取持久化权限 getContentResolver().takePersistableUriPermission( imageUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); imagePaths.add(imageUri.toString()); updateImagePreviews(); } else { Toast.makeText(this, "拍摄的图片未保存", Toast.LENGTH_LONG).show(); } } else if (result.getResultCode() == RESULT_CANCELED) { // 删除临时文件 new File(tmp_path).delete(); } } ); ``` ### 总结解决方案 1. **图片显示问题**: - 统一使用 `content://` URI - 添加持久化权限处理 (`takePersistableUriPermission`) - 自动转换文件路径为有效 URI - 增强 Glide 的错误处理 2. **拍照问题**: - 修复权限请求逻辑(处理 Android 13+ 的变化) - 确保临时文件创建可靠 - 添加文件存在性检查 - 完善 FileProvider 配置 - 添加取消拍照时的文件清理 3. **数据库改进**: - 自动更新旧文件路径为 content URI - 添加更健壮的错误处理 ### 额外建议 1. 使用 `MediaStore` API 代替直接文件操作 2. 添加内容观察器自动刷新图片 3. 对于大图加载使用缩略图优化 4. 添加磁盘缓存策略
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值