欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

程序员文章站 2024-01-20 15:45:34
...

 

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

目录

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

一、简单介绍

二、相关知识说明

三、注意实现

四、效果预览(演示为UnityEditor,测试过移动端同理可行)

五、实现步骤

六、关键代码


 

一、简单介绍

Unity中的一些基础知识点。

本节介绍,在 Unity 中,简单实现在移动端,读取与写入数据到本地,这以写入json数据为例,同时在写入和读取中,进行简单的数据加密和解密操作,便于后期使用,有不对,欢迎指正。

 

       Application.dataPath

  返回程序的数据文件所在文件夹的路径。例如在Editor中就是Assets了。  

  Application.streamingAssetsPath

  返回流数据的缓存目录,返回路径为相对路径,适合设置一些外部数据文件的路径。会随包导出。

  1. 只读不可写。

  2. 主要用来存放二进制文件。

  3. 只能用过WWW类来读取。

  Application.persistentDataPath

  返回一个持久化数据存储目录的路径,可以在此路径下存储一些持久化的数据文件。例如Assetbundle等资源

  1. 内容可读写,不过只能运行时才能写入或者读取。提前将数据存入这个路径是不可行的。

  2. 无内容限制。可以从StreamingAsset中读取二进制文件或者从AssetBundle读取文件来写入PersistentDataPath中。

 

二、相关知识说明

1、什么是FileStream类

  FileStream 类对文件系统上的文件进行读取、写入、打开和关闭操作,并对其他与文件相关的操作系统句柄进行操作,如管道、标准输入和标准输出。读写操作可以指定为同步或异步操作。FileStream 对输入输出进行缓冲,从而提高性能。——MSDN

  简单点说:FileStream类可以对任意类型的文件进行读取操作,而且我们也可以根据自己需要来指定每一次读取字节长度,以此减少内存的消耗,提高读取效率。

2、File和FileStream的区别

  直观点:File是一个静态类;FileStream是一个非静态类。

  File:是一个文件的类,对文件进行操作。其内部封装了对文件的各种操作(MSDN:提供用于创建、复制、删除、移动和打开单一文件的静态方法,并协助创建FileStream对象)。

  FileStream: 文件流的类。对txt,xml,avi等文件进行内容写入、读取、复制...时候需要使用的一个工具。

  打个形象的比喻。File是笔记本,需要Filestream的这个笔才能写.

  换而言之,记事本是一个文件,可以用File操作,里面的内容需要用FileStream来操作。

3、FileStream代码示例:

一、创建一个文件流
FileStream fs = new FileStream(@"c:\中国.txt", FileMode.Create, FileAccess.Write);

string txt = "中国是世界上人口第一大国。中国是世界上最幸福的国家之一。";
byte[] buffer = Encoding.UTF8.GetBytes(txt);

二、读文件或者写文件
//参数1:表示要把哪个byte[]数组中的内容写入到文件
//参数2:表示要从该byte[]数组的第几个下标开始写入,一般都是0
//参数3:要写入的字节的个数。
fs.Write(buffer, 0, buffer.Length);
//fs.Read(buffer, 0, buffer.Length);

三、关闭文件流
fs.flush();仅仅是清空缓冲区,流对象还可以继续使用。
fs.close();关闭流对象,但是先刷新一次缓冲区,关闭之后,流对象不可以继续再使用了。
fs.Dispose();//自动调用close和flush方法


简洁的写法

//一、创建一个文件流
//当把一个对象放到using()中的时候,当超出using的作用于范围后,会自动调用该对象的Dispose()方法。
using (FileStream fs = new FileStream(@"c:\中国.txt", FileMode.Create, FileAccess.Write))
{
    string txt = "中国是世界上人口第一大国。中国是世界上最幸福的国家之一。";
    byte[] buffer = Encoding.UTF8.GetBytes(txt);
    fs.Write(buffer, 0, buffer.Length);
}

 

4、Stream 流

Stream在msdn的定义:为字节序列提供通用的操作视图,这个解释太抽象了,不容易理解;从stream的字面意思“河,水流”更容易理解些,
Stream是一个抽象类,它定义了类似“水流”的事物的一些统一行为,包括这个“水流”是否可以抽水出来(读取流内容);是否可以往这个“水流”中注水(向流中写入内容);以及这个“水流”有多长;如何关闭“水流”,如何向“水流”中注水,如何从“水流”中抽水等“水流”共有的行为。

5、Stream的子类

  • 1.MemoryStream 存储在内存中的字节流。

  • 2.FileStream 存储在文件系统的字节流。

  • 3.NetworkStream 通过网络设备读写的字节流。

  • 4.BufferedStream 为其他流提供缓冲的流。

区别

  • BufferedStream 是为诸如网络 流的其它流添加缓冲的一种流类型。其实,FileStream流自身内部含有缓冲,而MemorySteam流则不需要缓冲。一个BufferStream 类的实例可以由多个其它类型的流复合而成,以达到提高性能的目的。缓冲实际上是内存中的一个字节块,利用缓冲可以避免操作系统频繁地到磁盘上读取数据,从而减轻了操作系统的负担。

  • MemoryStream 是一个无缓冲流,它所封装的数据直接放在内存中,因此可以用于快速临时存储、进程间传递信息等。
    这两个类都是缓冲区,都实现了对内存进行数据读写的功能,而不是对持久性存储器进行读写。
    但BufferedStream必须跟其他流如FileStream结合使用,而MemoryStream则不用。

  • Networksteam 表示在互联网络上传递的流。

  • FileStream 表示本地文件的读取和写入流

6、Stream的常用方法

属性/方法

用法

Length

获取用字节表示的流长度。

Position

获取或设置此流的当前位置。

Read(Byte[], Int32, Int32)

从流中读取字节块并将该数据写入给定缓冲区中。

ReadAsync(Byte[], Int32, Int32)

从当前流异步读取字节序列,并将流中的位置提升读取的字节数。

ReadByte()

从文件中读取一个字节,并将读取位置提升一个字节。

Seek(Int64, SeekOrigin)

将该流的当前位置设置为给定值。

Write(Byte[], Int32, Int32)

将字节块写入文件流。

WriteAsync(Byte[], Int32, Int32)

将字节序列异步写入当前流,并将流的当前位置提升写入的字节数。

WriteByte(Byte)

一个字节写入文件流中的当前位置。

Flush()

清除此流的缓冲区,使得所有缓冲数据都写入到文件中。

7、Reader And Write

Stream提供了读写流的方法是以字节的形式从流中读取内容。而我们经常会用到从字节流中读取文本或者写入文本,微软提供了StreamReader和StreamWriter类帮我们实现在流上读写字符串的功能。

  • BinaryReaderBinaryWriter 这两个类提供了从字符串或原始数据到各种流之间的读写操作。

  • TextReaderTextWriter 类都是抽象类。和Stream类的字节形式的输入和输出不同,它们用于Unicode字符的输入和输出。

  • StringReaderStringWriter 在字符串中读写字符。

  • StreamReaderStreamWriter 在流中读写字符。

 

三、注意实现

1、在没有读取数据的时候,这里临时读取网络接口的数据

2、这里的数据加密使用的是 Base64Code 的方法

3、Android 端读写入数据的位置是:Application.persistentDataPath(较适合移动端操作)

4、这里设计数据的 json 转换,使用的是 litjson 包,进行数据转换

 

四、效果预览(演示为UnityEditor,测试过移动端同理可行)

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

五、实现步骤

1、打开Unity,新建空工程

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

2、在场景中布局UI,测试数据的读取写入,和数据展示

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

3、导入 litjson,并且编写脚本,AndroidReadWriteDataWrapper 数据的读取和写入接口,以及可能用到的数据加密解密代码,MonoSingleton 单例类,TestAndroidReadWriteDataWrapper 测试 AndroidReadWriteDataWrapper  功能

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

4、把 TestAndroidReadWriteDataWrapper  挂载到场景中,并对应赋值

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

5、运行场景,打包到Android 设备上运行,效果如上

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

6、保存的加密数据,如下

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)

 

六、关键代码

1、AndroidReadWriteDataWrapper

using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

namespace XANTools
{
    public class AndroidReadWriteDataWrapper : MonoSingleton<AndroidReadWriteDataWrapper>
    {
        // 新闻数据的地址(本地没有数据的时候去网上取数据,根据需要设定的场景)
        string newsDataUrl = "https://launcher.madgaze.dev/v1/news/newsFeedList/cn";

        // 文件路径 (根据需要)
        string filePath;

        // 本地化保存是否加密标志信息
        const string PlayerPrefsName = "AndroidReadWriteDataWrapper" + "_" + "IsEncryption";
        private void Start()
        {
#if UNITY_EDITOR
            filePath = Application.dataPath + "/TestInfoFile.json";
#else
            filePath = Application.persistentDataPath + "/" + "TestInfoFile.jjson";
#endif
        }

        // 新闻数据字段
        NewsDataStructFromAPI _newsDataStructFromAPI;
        List<NewsDataStruct> _newsDataStructs;

        // 天气获取成功的事件
        Action<List<NewsDataStruct>> GetNewsDataSucessAction;
        Action<string> GetNewsDataFailAction;

        // 新闻数据属性
        public NewsDataStructFromAPI NewsDataStructFromAPI { get => _newsDataStructFromAPI; private set => _newsDataStructFromAPI = value; }
        public List<NewsDataStruct> NewsDataStructs { get => _newsDataStructs; private set => _newsDataStructs = value; }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="getNewsDataSucessHandler"></param>
        /// <param name="getNewsDataFailHandler"></param>
        public void Read(Action<List<NewsDataStruct>> getNewsDataSucessHandler, Action<string> getNewsDataFailHandler) {

            Read(filePath,getNewsDataSucessHandler,getNewsDataFailHandler);

        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public void Read(string path, Action<List<NewsDataStruct>> getNewsDataSucessHandler, Action<string> getNewsDataFailHandler)
        {

            GetNewsDataSucessAction = getNewsDataSucessHandler;
            GetNewsDataFailAction = getNewsDataFailHandler;

            // 文件存在与否
            if (File.Exists(path) == false)
            {
                StartCoroutine(GetRequest(newsDataUrl));
            }
            else
            {
                StartCoroutine(ReadFile(path));
            }


        }

        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="newsDataStructs"></param>
        public void Write(List<NewsDataStruct> newsDataStructs) {
            Write(filePath,newsDataStructs);
        }

        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="path"></param>
        /// <param name="newsDataStructs"></param>
        public void Write(string path, List<NewsDataStruct> newsDataStructs, bool isEncryption = false) {
            StartCoroutine(WriteFile(path, newsDataStructs, isEncryption));
        }

        /// <summary>
        /// 获取新闻数据
        /// </summary>
        /// <returns></returns>
        public List<NewsDataStruct> GetNewsData() {

            return NewsDataStructs;
        }

        #region 内部私有方法

        /// <summary>
        /// 获取网络数据
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        IEnumerator GetRequest(string url)
        {

            using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
            {
                Debug.Log(GetType() + "/UnityWebRequest.Get()/");

                yield return webRequest.SendWebRequest();

                // 可以放在 Update 中显示 
                //Debug.Log("webRequest.uploadProgress " + webRequest.uploadProgress);

                if (webRequest.isHttpError || webRequest.isNetworkError)
                {
                    Debug.LogError(webRequest.error + "\n" + webRequest.downloadHandler.text);

                    // 执行回调
                    if (GetNewsDataFailAction != null)
                    {
                        GetNewsDataFailAction(webRequest.downloadHandler.text);
                    }
                }
                else
                {
                    string weatherJsonStr = webRequest.downloadHandler.text;
                    Debug.Log(GetType() + "/GetRequest()/ JsonStr : " + weatherJsonStr);


                    // 解析数据
                    NewsDataStructFromAPI = JsonMapper.ToObject<NewsDataStructFromAPI>(weatherJsonStr);
                    NewsDataStructs = NewsDataStructFromAPI.data;
                    if (NewsDataStructs == null)
                    {
                        Debug.Log(GetType() + "/GetRequest/ NewsDataStructs ==null");
                    }
                    else
                    {
                        Debug.Log(GetType() + "/GetRequest()/ NewsDataStructs[0].name :" + NewsDataStructs[0].name);

                    }


                    // 执行回调
                    if (GetNewsDataSucessAction != null)
                    {
                        GetNewsDataSucessAction(NewsDataStructs);
                    }
                }
            }



        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        IEnumerator ReadFile(string path) {

            yield return new WaitForEndOfFrame();
            try
            {
                FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
                StreamReader sr = new StreamReader(fs, Encoding.UTF8);
                string data = sr.ReadToEnd();

                // 加密的数据,需要解密
                bool IsEncryption = (PlayerPrefs.GetInt(PlayerPrefsName,0)==1)?true:false;
                if (IsEncryption == true)
                {
                    data = Base64CodeDecryption(data);
                }

                NewsDataStructs = JsonMapper.ToObject<List<NewsDataStruct>>(data);
                // 执行回调
                if (GetNewsDataSucessAction != null)
                {
                    GetNewsDataSucessAction(NewsDataStructs);
                }
                sr.Close();
                fs.Close();
            }
            catch (IOException e)
            {
                Debug.LogError(GetType() + "/ReadFile()/FileRead: " + e.Message);
            }
        }

        /// <summary>
        /// 写入文件
        /// </summary>
        /// <param name="path"></param>
        /// <param name="newsDataStructs"></param>
        /// <returns></returns>
        IEnumerator WriteFile(string path, List<NewsDataStruct> newsDataStructs, bool isEncryption) {
           
            // 本地化保存是否加密标志信息
            PlayerPrefs.SetInt(PlayerPrefsName,isEncryption?1:0);
            PlayerPrefs.Save();

            yield return new WaitForEndOfFrame();
            try
            {
                FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
                StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
                string data = JsonMapper.ToJson(newsDataStructs);

                // 加密数据
                if (isEncryption == true)
                {
                    data = Base64CodeEncryption(data);
                }

                sw.WriteLine(data);
                sw.Close();
                fs.Close();
            }
            catch (Exception ex)
            {
                Debug.LogError(GetType() + "/WriteFile()/Write File: " + ex.Message);
            }
        }



       
        /// <summary>
        /// Base64位对称加密
        /// </summary>
        /// <param name="data">要加密的数据</param>
        /// <returns></returns>
        string Base64CodeEncryption(string data)
        {         
            byte[] byteStr = System.Text.Encoding.UTF8.GetBytes(data);
            string str = Convert.ToBase64String(byteStr);//转换后的64位字符串
            Debug.Log(GetType()+ "/Base64CodeEncryption()/Base64加密后的数据:" + str);

            return str;
        }

        /// <summary>
        /// Base64位对称解密
        /// </summary>
        /// <param name="data">要解密的数据</param>
        /// <returns></returns>
        string Base64CodeDecryption(string data) {
            //解密
            byte[] temp = Convert.FromBase64String(data);
            string str = System.Text.Encoding.UTF8.GetString(temp);//64位恢复字符串
            Debug.Log(GetType() + "/Base64CodeDecryption()/Base64解密后的数据:" + str);

            return str;
        }

        #endregion 内部私有方法
    }

    public class NewsDataStructFromAPI
    {
        public int code;
        public string msg;
        public List<NewsDataStruct> data;

        public NewsDataStructFromAPI() { }
    }

    public class NewsDataStruct
    {
        public string name;
        public string url;
        public string icon;
        public bool IsLoved;

        public NewsDataStruct() { }

        public NewsDataStruct(string name, string url, string icon, bool isLoved)
        {
            this.name = name;
            this.url = url;
            this.icon = icon;
            IsLoved = isLoved;
        }

        public override string ToString()
        {
            return string.Format("Name:{0},Url:{1},IconUrl:{2},IsLoved:{3}", name, url, icon, IsLoved);
        }
    }
}

/* 数据格式
 {
	"code": 0,
	"msg": "success",
	"data": [{
		"name": "人民网",
		"url": "http://www.people.com.cn/",
		"icon": "http://file.madgaze.cn/assets/launcher/bbc-test.png"
	}, {
		"name": "凤凰网",
		"url": "http://news.ifeng.com/",
		"icon": "http://file.madgaze.cn/assets/launcher/bbc-test.png"
	}, {
		"name": "央视网",
		"url": "https://news.cctv.com/",
		"icon": "http://file.madgaze.cn/assets/launcher/bbc-test.png"
	}, {
		"name": "网易新闻",
		"url": "https://news.163.com/",
		"icon": "http://file.madgaze.cn/assets/launcher/bbc-test.png"
	}]
}
     
     */

 

2、MonoSingleton

using UnityEngine;

public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance = null;

    private static readonly object locker = new object();

    private static bool bAppQuitting;

    public static T Instance
    {
        get
        {
            if (bAppQuitting)
            {
                instance = null;
                return instance;
            }

            lock (locker)
            {
                if (instance == null)
                {
                    // 保证场景中只有一个 单例
                    T[] managers = Object.FindObjectsOfType(typeof(T)) as T[];
                    if (managers.Length != 0)
                    {
                        if (managers.Length == 1)
                        {
                            instance = managers[0];
                            instance.gameObject.name = typeof(T).Name;
                            return instance;
                        }
                        else
                        {
                            Debug.LogError("Class " + typeof(T).Name + " exists multiple times in violation of singleton pattern. Destroying all copies");
                            foreach (T manager in managers)
                            {
                                Destroy(manager.gameObject);
                            }
                        }
                    }


                    var singleton = new GameObject();
                    instance = singleton.AddComponent<T>();
                    singleton.name = "(singleton)" + typeof(T);
                    singleton.hideFlags = HideFlags.None;
                    DontDestroyOnLoad(singleton);

                }
                instance.hideFlags = HideFlags.None;
                return instance;
            }
        }
    }

    protected virtual void Awake()
    {
        bAppQuitting = false;

        
    }

    protected virtual void OnDestroy()
    {
        bAppQuitting = true;
    }
}

 

3、TestAndroidReadWriteDataWrapper

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using XANTools;

public class TestAndroidReadWriteDataWrapper : MonoBehaviour
{
    public Text Data_Text;
    public Button Read_Button;
    public Button Write_Button;

    string filePath ;

    private List<NewsDataStruct> _newsDataStructs;

    // Start is called before the first frame update
    void Start()
    {
#if UNITY_EDITOR
        filePath = Application.dataPath + "/TestInfoFile.json";
#else
        filePath = Application.persistentDataPath + "/" + "TestInfoFile.jjson";
#endif
        Read_Button.onClick.AddListener(Read);
        Write_Button.onClick.AddListener(Write);
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void Read() {

        Debug.Log(GetType() + "/Read()");
        AndroidReadWriteDataWrapper.Instance.Read(filePath,(newsDatas)=> {
            Data_Text.text = "";
            _newsDataStructs = newsDatas;
            foreach (NewsDataStruct item in newsDatas)
            {
                Data_Text.text += item.ToString() + "\n";
            }

        },(failInfo)=> {
            Data_Text.text = failInfo;
        });
    }

    void Write() {
        Debug.Log(GetType() + "/Write()");
        if (_newsDataStructs != null)
        {
            AndroidReadWriteDataWrapper.Instance.Write(filePath, _newsDataStructs,true);

        }
        else {
            Debug.Log(GetType()+ "/Write()/_newsDataStructs == null");
        }
    }
}

 

Unity 基础 之 实现简单的Android移动端本地数据读取与写入封装(简单加密写入,解密读取 json 数据)