Android 应用开发-实现将公共存储空间内的文件复制到应用的私用存储空间中

2024-05-16 01:52

本文主要是介绍Android 应用开发-实现将公共存储空间内的文件复制到应用的私用存储空间中,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、前言

几个月前,我用Android Studio给公司销售部门的同事开发了一款手机app,让同事们用自己的手机就能进行商品的扫码盘点操作,帮他们提高了工作效率,他们用了一段时间,反映还不错。不过前几天,销售部门的同事找到我,说近期公司新增了一些商品,用我的这款软件无法正常扫码这些新商品,希望我能解决问题。

  

这个问题的产生原因是,因为我的能力和资源有限,开发的这款手机app只是一个单机版的辅助工具。在开发时,商品信息是以Sqlit3数据库文件的形式保存在raw文件夹下,随代码一并打包在apk里,软件安装后第一次运行时,会将raw文件夹内的数据库文件导入到应用的私有存储空间内,等于是内嵌在软件中的。这个应用提供了一个手工添加商品信息的功能,但只能一个个手工添加,遇到大量新增的商品信息会很麻烦。这次新增了400多个商品信息,因此同事赶紧找我们解决这个问题。

这篇日志记录了解决问题的过程以备忘。

二、实现过程

要解决这个问题,最简单的方法就是将raw文件夹里的数据库文件更新,再重新打包成apk后,让同事重装一下。但使用这个软件的同事有好些个,有些同事觉得这样的话,以后要是更新商品就要重装一次应用,觉得不妥。希望我还是提供批量添加新商品信息的功能。

鉴于此,我决定为这个应用开发一个导入商品信息的功能。该功能要达到的目标是,在商品更新后,由与我对接的同事按我要求的模板将商品信息做成一个xls文件,然后通过通信软件(如钉钉、微信)将这个xls文件发送给其他的同事。其他同事在手机上下载这个xls文件,然后进入本应用的导入商品模块,选择这个xls文件,将文件内的数据导入的应用内数据库文件中。这样的好处是,以后的商品更新操作都可以由同事们自己完成,不用我插手了。我按这个思路在网上搜索了相关的知识,我希望使用第三方的poi库读取xls文件的数据内容(因为之前开发的将db文件中将数据导出为xls文件就是用的poi库),然后将获取到的数据更新到应用的私有空间内。网上这方面的内容还是比较容易找到,将搜索到的几篇文章中的知识点了解清楚后,依瓢画葫芦就完成了这个功能。通过真机调试,实现了从选定的数据功能。

(Android Studio连真机调试,成功将xls文件中的数据添加到数据库中)

但是在我将应用打包成apk发给同事进行测试时,在所有测试的同事手机上使用该功能导入数据时都出现了闪退现象,这让我很疑惑,之后我将apk文件安装到我的手机上,也出现了闪退,我将手机连接电脑,通过Android Studio检查错误信息,看到了如下的错误提示:

(执行导入商品操作时出现的错误提示)

通过对错误提示的分析,发现是执行语句“InputStream input = contentResolver.openInputStream(fileUri);”时出的错,获取到的对象为空。但是,我之后又通过Android Studio连真机执行“run”命令,用调试模式覆盖掉apk安装文件后,再测试又是正常的。反复进行安装apk和Android Studio连真机调试,都是安装apk后执行导入功能就闪退,调试模式下正常,这就很郁闷了。网上又找不要引起这个问题的原因,导致开发受阻。

但问题总要尽快解决,不然会让这款应用的使用感受严重下降。不得已我要另寻他法。

之后我转换一下思路,开发一个替换数据库文件的功能来解决问题。这个功能的要达到的目标是,在商品信息发生变化后,由与我对接的同事将新的商品信息提供给我,我做好db数据库文件,返回给他,由他通过通信软件(如钉钉、微信)将这个db文件发送给其他的同事。其他同事在手机上下载db文件,然后进入本应用的更新数据模块,选择这个db文件,将文件内容读取出来覆盖应用内原来的数据库文件,达到更新商品信息的目的。读取db文件不会用到poi库,有可能避开上面的问题。有了之前的开发经验,只需要对原来的代码做适当的修改就得到了新的功能,比之前的开发快了不少。在完成了新模块的开发后,再次进行真机调试和打包apk测试,均正常实现了从公共存储空间将db文件的内容复制到应用私有空间中,没有出现闪退问题。

 

(将db文件内容覆盖了原数据库)    (更新后可以查询到新增的商品信息)

这样做,就绕开了上面读取xls文件出错的问题,但我的同事不会做db数据库文件,还需要我插手,相比前面的方案要略逊一筹。但至少同事提出的批量更新商品信息到手机内嵌的数据库中的需求得到了解决。以后如果找到了前述问题的解决办法,再做处理。有时候遇到些问题并不一定是坏事,在解决问题的过程中,也能学到很多的知识

三、部分代码展示

更新数据功能模块xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"android:background="@color/gray_245"android:orientation="vertical"tools:context=".UpdateDbActivity"><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/yellow_455"app:navigationIcon="@drawable/ic_back_b" ><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:textColor="@color/black"android:textSize="24sp"android:textStyle="bold"android:text="@string/GoodsInfoActivity_update" /></androidx.appcompat.widget.Toolbar><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical" ><Buttonandroid:id="@+id/btn_select"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginTop="20dp"android:text="@string/select" /><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:text="选择DB文件,才能更新商品信息数据。"android:textSize="16sp"  /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:padding="0dp"android:background="@drawable/shape_round_bg_white"android:orientation="vertical"><TextViewandroid:id="@+id/tv_fileInfo"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:paddingStart="10dp"android:paddingEnd="10dp"android:layout_marginBottom="20dp"android:text="@string/fileInfo"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_fileName"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:paddingStart="10dp"android:paddingEnd="10dp"android:layout_marginBottom="20dp"android:text="@string/fileName"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_fileSize"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:paddingStart="10dp"android:paddingEnd="10dp"android:layout_marginBottom="20dp"android:text="@string/fileSize"android:textSize="16sp" /></LinearLayout><Buttonandroid:id="@+id/btn_updateDB"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginBottom="20dp"android:text="@string/updateDB" /><TextViewandroid:id="@+id/tv_tips"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginBottom="20dp"android:text="" /></LinearLayout></LinearLayout>

功能模块java文件

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.TextView;
import android.widget.Toast;import com.bahamutj.easyinventory.database.GoodsDbHelper;import java.util.Locale;public class UpdateDbActivity extends AppCompatActivity implements View.OnClickListener {private int state = 0;  // 状态,0-初始状态,1-已选择了文件,2-已完成导入,-1-异常状态private final static int DB_CODE = 1;private Uri fileUri;private TextView tv_fileInfo, tv_fileName,tv_fileSize, tv_tips;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_update_db);Toolbar toolbar = findViewById(R.id.toolbar);  // 工具栏toolbar.setTitle("");setSupportActionBar(toolbar);  // 要导入androidx.appcompat.widget.Toolbar 否则报错toolbar.setNavigationOnClickListener(view -> {finish(); // 结束当前页面});tv_fileInfo = findViewById(R.id.tv_fileInfo);tv_fileName = findViewById(R.id.tv_fileName);tv_fileSize = findViewById(R.id.tv_fileSize);tv_tips = findViewById(R.id.tv_tips);findViewById(R.id.btn_select).setOnClickListener(this);  // 选择文件按钮设置监听findViewById(R.id.btn_updateDB).setOnClickListener(this);  // 导入商品按钮设置监听}@Overridepublic void onClick(View v) {int id = v.getId();if ( id == R.id.btn_select) {  // 选择DB文件this.tv_tips.setText("");// 打开Download目录UriUri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download");Intent intent = new Intent(Intent.ACTION_GET_CONTENT);intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选intent.addCategory(Intent.CATEGORY_OPENABLE);// 设置文件类型String[] mimetypes = {"application/octet-stream", "application/x-sqlite3","application/vnd.sqlite3", "application/x-trash"};intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes);  // 设置文件格式intent.setType("*/*");intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);startActivityForResult(intent, DB_CODE);} else if ( id == R.id.btn_updateDB ) {  // 更新数据if (this.state == 1) {this.tv_tips.setText("更新数据中...");// 更新数据库GoodsDbHelper dbHelper = new GoodsDbHelper(this);int result = dbHelper.copyDatabase(this, this.fileUri);if (result ==1) {this.state = 2;  // 设置状态this.tv_tips.setText("数据已完成更新。");} else {this.tv_tips.setText("数据更新失败。");}} else if (this.state == 0) {Toast.makeText(this, "未选择文件", Toast.LENGTH_SHORT).show();this.tv_tips.setText("");} else if (this.state == 2) {Toast.makeText(this, "数据已更新,不能重复更新", Toast.LENGTH_SHORT).show();this.tv_tips.setText("");}}}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK && data != null) {this.tv_tips.setText("");Uri uri = data.getData();  // 获取文件uriString uriString = uri.toString();// String type = getContentResolver().getType(uri);  // 获取到文件的类型// 检查文件的扩展名String extension = MimeTypeMap.getFileExtensionFromUrl(uriString);if (!extension.equals("db")) {Toast.makeText(this, "选择的不是db格式数据库文件", Toast.LENGTH_SHORT).show();Toast.makeText(this, "未获取到文件", Toast.LENGTH_SHORT).show();this.tv_fileInfo.setText("文件信息:");this.tv_fileName.setText("文件名称:");this.tv_fileSize.setText("文件大小:");this.state = -1;} else {String fileName;// 获取文件路径、文件名称和文件大小String fileInfo = uri.getPath();fileName = this.getFileName(this, uri);long fileSize = this.getFileSize(this, uri) / 1024;this.tv_fileInfo.setText(String.format(Locale.CHINESE, "%s%s", "文件信息:\n" , fileInfo));this.tv_fileName.setText(String.format(Locale.CHINESE, "%s%s", "文件名称:" , fileName));this.tv_fileSize.setText(String.format(Locale.CHINESE, "%s%d%s", "文件大小:", fileSize, "KB"));this.state = 1;  // 设置状态this.fileUri = uri;}} else {tv_fileInfo.setText("文件信息:");this.tv_fileName.setText("文件名称:");this.tv_fileSize.setText("文件大小:");this.state = 0;}}// 从uri获取文件的名称private String getFileName(Context context, Uri uri) {Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);/** Get the column indexes of the data in the Cursor,* move to the first row in the Cursor, get the data,* and close the Cursor.*/int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);returnCursor.moveToFirst();String name = (returnCursor.getString(nameIndex));returnCursor.close();return name;}// 从uri获取文件的大小private long getFileSize(Context context, Uri uri) {Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);/** Get the column indexes of the data in the Cursor,* move to the first row in the Cursor, get the data,* and close the Cursor.*/int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);returnCursor.moveToFirst();long size = (returnCursor.getLong(sizeIndex));returnCursor.close();return size;}}
GoodsDbHelper.java部分代码
public class GoodsDbHelper extends SQLiteOpenHelper {private static final String TAG = "GoodsDbHelper";private static final String DB_NAME = "easy_joy.db";public static final String TABLE_NAME = "tb_goods"; // 表的名称public static final String PACKAGE_NAME = "com.bahamutj.easyinventory";// 下面的设置,数据库的位置为(/data/data/com.bahamutj.easyinventory/databases/easy_joy.db)public static final String DB_PATH =  "/data"+ Environment.getDataDirectory().getAbsolutePath() + "/" + PACKAGE_NAME+ "/databases/";public static final String DB_FILE = DB_PATH + DB_NAME;private static final int DB_VERSION = 1;private SQLiteDatabase mDB = null;  // 数据库的实例public static GoodsDbHelper mHelper = null;  // 数据库帮助器的实例//private final Context mContext;public GoodsDbHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);//mContext = context;}@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase) {// 在这里可以创建数据库表格,如果不需要可留空}@Overridepublic void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {// 在这里可以处理数据库升级的逻辑,如果不需要可留空}// 利用单例模式获取数据库帮助器的唯一实例,防止重复获取public static GoodsDbHelper getInstance(Context context) {if (mHelper == null) {mHelper = new GoodsDbHelper(context);}return mHelper;}// 省略......// 复制用户选择的数据库文件到应用的数据库路径中public int copyDatabase(Context mContext, Uri fileUri){int result = 0;ContentResolver contentResolver = mContext.getContentResolver();try {InputStream inputStream = contentResolver.openInputStream(fileUri);OutputStream outputStream = new FileOutputStream(mContext.getDatabasePath(DB_NAME));byte[] buffer = new byte[2048];  // 设置缓存大小int length;while ((length = inputStream.read(buffer)) > 0) {outputStream.write(buffer, 0, length);}outputStream.flush();outputStream.close();inputStream.close();result = 1;} catch (IOException e) {e.printStackTrace(); }return result;}}    

这篇关于Android 应用开发-实现将公共存储空间内的文件复制到应用的私用存储空间中的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/993539

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

公共筛选组件(二次封装antd)支持代码提示

如果项目是基于antd组件库为基础搭建,可使用此公共筛选组件 使用到的库 npm i antdnpm i lodash-esnpm i @types/lodash-es -D /components/CommonSearch index.tsx import React from 'react';import { Button, Card, Form } from 'antd'

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

亮相WOT全球技术创新大会,揭秘火山引擎边缘容器技术在泛CDN场景的应用与实践

2024年6月21日-22日,51CTO“WOT全球技术创新大会2024”在北京举办。火山引擎边缘计算架构师李志明受邀参与,以“边缘容器技术在泛CDN场景的应用和实践”为主题,与多位行业资深专家,共同探讨泛CDN行业技术架构以及云原生与边缘计算的发展和展望。 火山引擎边缘计算架构师李志明表示:为更好地解决传统泛CDN类业务运行中的问题,火山引擎边缘容器团队参考行业做法,结合实践经验,打造火山

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

android 免费短信验证功能

没有太复杂的使用的话,功能实现比较简单粗暴。 在www.mob.com网站中可以申请使用免费短信验证功能。 步骤: 1.注册登录。 2.选择“短信验证码SDK” 3.下载对应的sdk包,我这是选studio的。 4.从头像那进入后台并创建短信验证应用,获取到key跟secret 5.根据技术文档操作(initSDK方法写在setContentView上面) 6.关键:在有用到的Mo

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

Android我的二维码扫描功能发展史(完整)

最近在研究下二维码扫描功能,跟据从网上查阅的资料到自己勉强已实现扫描功能来一一介绍我的二维码扫描功能实现的发展历程: 首页通过网络搜索发现做android二维码扫描功能看去都是基于google的ZXing项目开发。 2、搜索怎么使用ZXing实现自己的二维码扫描:从网上下载ZXing-2.2.zip以及core-2.2-source.jar文件,分别解压两个文件。然后把.jar解压出来的整个c