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

news2024/9/23 3:28:36

一、前言

几个月前,我用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.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/yellow_455"
        app:navigationIcon="@drawable/ic_back_b" >

        <TextView
            android: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>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <Button
            android: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" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="选择DB文件,才能更新商品信息数据。"
            android:textSize="16sp"  />

        <LinearLayout
            android: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">
            <TextView
                android: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" />

            <TextView
                android: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" />

            <TextView
                android: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>


        <Button
            android: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" />

        <TextView
            android: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;

    @Override
    protected 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);  // 导入商品按钮设置监听
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if ( id == R.id.btn_select) {  // 选择DB文件
            this.tv_tips.setText("");
            // 打开Download目录Uri
            Uri 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("");
            }
        }
    }

    @Override
    protected 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();  // 获取文件uri
            String 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;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // 在这里可以创建数据库表格,如果不需要可留空
    }
    @Override
    public 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;
    }

}    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql 事务隔离级别

前言 在数据库管理系统中&#xff0c;事务&#xff08;Transaction&#xff09;是保证数据一致性和完整性的重要机制。在并发环境下&#xff0c;多个事务同时操作相同的数据可能会引发各种问题&#xff0c;如脏读、不可重复读、幻读等。为了解决这些问题&#xff0c;MySQL提供…

腾讯开源混元DiT文生图模型,消费级单卡可推理

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接…

script标签以及defer和async属性

1. <script>标签 将JavaScript代码嵌入到HTML中主要方式是使用<script>元素。 使用<script>的方式有两种&#xff1a; &#xff08;1&#xff09;直接在网页中嵌入JavaScript代码&#xff1a; <script>function sayHi() {console.log("Hi"…

基于springboot+vue+Mysql的大学生社团活动平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

PCIE协议-2-事务层规范-Transaction Ordering

2.4.1 事务排序规则 表2-40定义了PCI Express事务的排序要求。此表中定义的规则适用于PCI Express上的所有事务类型&#xff0c;包括内存、I/O、配置和消息事务。在单个流量类别&#xff08;Traffic Class&#xff0c;TC&#xff09;内&#xff0c;这些排序规则适用。不同TC标…

VUE之旅—day2

文章目录 Vue生命周期和生命周期的四个阶段created应用—新闻列表渲染mounted应用—进入页面搜索框就获得焦点账单统计&#xff08;Echarts可视化图表渲染&#xff09; Vue生命周期和生命周期的四个阶段 思考&#xff1a; 什么时候可以发送初始化渲染请求&#xff1f;&#xff…

经验分享智能产品从0到1全流程

大家好&#xff0c;今天继续分享文章&#xff0c;这篇文章在网络上搜索资料时&#xff0c;有感而发&#xff0c;分享一个智能产品从0到1的整个生命周期中需要经历哪些阶段&#xff0c;我这里以开发一个mini补光灯为例&#xff0c;深入探索各个阶段可能涉及的具体活动和考虑事项…

记录计全支付切换到RabbitMQ时启动报错的问题

记录计全支付切换到RabbitMQ时启动报错的问题 首先在application.yml中切换到RabbitMQ配置安装RabbitMQ、Erlang、延时插件 rabbitmq_delayed_message_exchange&#xff0c;延迟插件必装 首先在application.yml中切换到RabbitMQ配置 # 第一处rabbitmq:addresses: 127.0.0.1:56…

TRL校准和De-embedding的区别以及如何操作?

Fiture的性能可以在测试前利用TRL校准件移除掉&#xff0c;但是TRL的步骤比较繁琐或者说TRL校准件&#xff08;包含直通、反射、多条Line&#xff09;很难设计(如果做到很高的频率对设计和加工制造的要求都很高)&#xff0c;此时可以选择只做一根2x Through&#xff08;直通件&…

电脑常用的PDF阅读器-嗨动PDF编辑器!带你详细了解它

电脑常用的PDF阅读器-嗨动PDF编辑器&#xff01;在数字化信息爆炸的时代&#xff0c;PDF格式的文件因其易于打印和保留原始格式等优点&#xff0c;成为了人们日常工作和学习的常用格式。而对于PDF文件的处理&#xff0c;一款功能强大、操作简便的PDF阅读器是必不可少的。今天&a…

世界500强企业建设软件开发安全体系,打造DevSecOps示范标杆

某世界500强企业旗下拥有众多知名汽车品牌&#xff0c;业务涵盖出行服务、科技创新、金融服务、教育等。该集团一直专注于技术创新和人才培养&#xff0c;同时也是国内第一批倡议并践行“数字化转型”的汽车行业企业。 数智化建设转型&#xff0c;研发安全体系建设势在必行 在…

C#【进阶】委托和事件

委托和事件 文章目录 1、委托1、委托概念2、基本语法3、定义自定义委托4、使用自定义委托5、委托变量可以存储多个函数6、系统定义好的委托思考 怪物死亡数据更新 2、事件1、事件概念2、事件的使用3、为什么有事件思考 热水器 3、匿名函数1、匿名函数概念2、基本语法3、使用4、…

27_Scala功能函数

文章目录 功能函数1.功能函数处理集合数据2.扁平化操作3.按照指定条件将数据集中的数据进行过滤4.集合通过 自定义函数进行分组5.mapValues6.sortBy函数 功能函数 1.功能函数处理集合数据 –集合的功能函数 map List --> map( logical ) --> newList–实现一个不确定的…

Blender雕刻建模流程

1.构形 先构造一个大致相像的外形 可使用的方法包含 -多边形&#xff1a;表面细分&#xff0c;布尔 -曲线&#xff1a;曲线倒角 -融球&#xff08;使用较少&#xff09; -曲面&#xff08;使用较少&#xff09; 构形之后的准备 -应用缩放 -应用修改器 -曲线转网格 1.1…

8个迹象表明你需要一台新笔记本电脑,看一下你的笔记本是否有其中一个

序言 当你第一次打开你的笔记本电脑的盒子时,它会以最高性能运行,电池寿命更长,过热最小,资源使用效率高。然而,随着笔记本电脑的老化,它将不能满足预期用途。以下几个迹象表明,可能是时候寻找并投资一款新设备了。 你的设备不再具有预期用途 如果你的笔记本电脑不再…

战网国际服注册教程 暴雪战网国际服账号注册一站式教程分享

战网国际版&#xff0c;也即Battle.net环球版&#xff0c;是由暴雪娱乐操刀的全球化游戏交流枢纽&#xff0c;它突破地理限制&#xff0c;拥抱全世界的游戏玩家。与仅限特定地区的版本不同&#xff0c;国际版为玩家开辟了无障碍通道&#xff0c;让他们得以自由探索暴雪庞大游戏…

MacOS docker 安装与配置

orbstack 安装 官网&#xff1a; https://orbstack.dev 下载链接&#xff1a;Download OrbStack Fast, light, simple Docker Desktop alternative 选择是Apple M系列处理器&#xff0c; 或 Intel系列处理器 到这里就安装好了Orbstack软件&#xff0c;下面开始配置docker 下…

C语言/数据结构——栈的实现

一.前言 今天我们讲解新的领域——栈。 二.正文 1.栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其允许在固定的一段进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#…

css笔记总结2

找到所有的 h1 标签。 选择器&#xff08;选对人&#xff09; 设置这些标签的样式&#xff0c;比如颜色为红色&#xff08;做对事&#xff09;。 ##css基础选择器 基础选择器又包括&#xff1a;标签选择器、类选择器、id 选择器和通配符选择器 ###标签选择器&#xff1a; 标签…

PostgreSQL(十二)报错:Tried to send an out-of-range integer as a 2-byte value: 51000

目录 一、报错场景二、源码分析三、实际原因&#xff08;更加复杂&#xff09;四、解决思路 一、报错场景 今天写了一个历史数据处理程序&#xff0c;在开发环境、测试环境都可以正常执行&#xff0c;但是放到生产环境上就不行&#xff0c;报了一个这样的错误&#xff1a; or…