Version: 3.8.0
Language: TypeScript
Environment: Mac
Introduction
In cocosCreator, the localStorage
interface is mainly used for local storage, and data is stored and read in the key-value format.
The main interfaces are:
Interface | Description |
---|---|
setItem(key, value ) | Save the data of the specified index |
getItem(key) | Get Data at the specified index |
removeItem(key) | Remove data at the specified index |
clear() | Clear all data |
The definition file is as follows:
//cc.d.ts export const sys: {<!-- --> // The local storage function of localStorage in the HTML5 standard is equivalent to window.localStorage on the Web side localStorage: Storage; } //lib.dom.d.ts interface Storage {<!-- --> // Return the number of data items readonly length: number; // Remove all stored data clear(): void; // Get data based on key name, null if not getItem(key: string): string | null; // Get the key name at the specified index, null if not found key(index: number): string | null; //Remove specified data based on key name removeItem(key: string): void; // Store the key name and data. Note that the storage may be full, which will throw an exception. setItem(key: string, value: string): void; [name: string]: any; }
In cocosCreator, local data is stored in sqlite database format.
Let’s use setItem
to briefly look at the engine’s packaging:
- C++ related, directory in: …/engine-native/cocos/storage/local-storage
// LocalStorage.cpp void localStorageSetItem(const std::string & amp;key, const std::string & amp;value) { assert(_initialized); int ok = sqlite3_bind_text(_stmt_update, 1, key.c_str(), -1, SQLITE_TRANSIENT); ok |= sqlite3_bind_text(_stmt_update, 2, value.c_str(), -1, SQLITE_TRANSIENT); ok |= sqlite3_step(_stmt_update); ok |= sqlite3_reset(_stmt_update); if (ok != SQLITE_OK & amp; & amp; ok != SQLITE_DONE) printf("Error in localStorage.setItem()\\ "); }
- Related to the Android platform, the directory is in …/libcocos/intermediates/javac/com/cocos/lib
// LocalStorage-android.cpp void localStorageSetItem(const std::string & amp;key, const std::string & amp;value) {<!-- --> assert(gInitialized); JniHelper::callStaticVoidMethod(JCLS_LOCALSTORAGE, "setItem", key, value); } // CocosLocalStorage.class public static void setItem(String key, String value) {<!-- --> try {<!-- --> String sql = "replace into " + TABLE_NAME + "(key,value)values(?,?)"; mDatabase.execSQL(sql, new Object[]{<!-- -->key, value}); } catch (Exception var3) {<!-- --> var3.printStackTrace(); } }
Simply look at the internal implementation and understand that local data is stored in the sqlite database.
Use
Local storage is used in scripts. Commonly used interfaces are:
-
setItem(key: string, value: string): void;
Store data -
getItem(key: string): string | null
Get data -
removeItem(key: string): void;
Remove data
Simple example:
const key = "Debug_Storage"; // save data sys.localStorage.setItem(key, "cocosCreator"); // retrieve data let value = sys.localStorage.getItem(key); console.log("----- The stored data is:", value);
Notice:
- The data stored in setItem is of type
string
, so when storing data, pay attention to the data type conversion - The storage of setItem is full. Please pay attention to the occurrence of exceptions.
- getItem obtains data as
string
ornull
, pay attention to the safety judgment of the returned data
Therefore, encapsulation management of localStorage
can be added to the project to support:
-
Supports storage of different basic data types, including but not limited to
string
type, just use data conversion -
Supports storage of complex data types such as arrays and Maps, using Json conversion
-
Data reading, supports default data settings
The main interface for Json conversion:
JSON.stringify
Convert data to Json stringJSON.parse
is used to parse Json strings into data
The main implementation logic is as follows:
import {<!-- --> _decorator, sys} from 'cc'; const {<!-- --> ccclass, property } = _decorator; export class StorageManager {<!-- --> private static _instance: StorageManager = null; static get instance() {<!-- --> if (this._instance) {<!-- --> return this._instance; } this._instance = new StorageManager(); return this._instance; } // save data public setItem(key: string, value:any) {<!-- --> if (value === undefined || value === null) {<!-- --> console.log(`Local storage data is illegal, key:${<!-- -->key}`); return; } let valueType = typeof(value); if (valueType === "number" & amp; & amp; isNaN(value)) {<!-- --> console.log(`Local storage data is NaN, key:${<!-- -->key}`); return; } // Convert data if (valueType === "number") {<!-- --> value = value.toString(); } else if (valueType === "boolean") {<!-- --> // Convert boolean type to 0 or 1 value = value ? "1" : "0"; } else if (valueType === "object") {<!-- --> // Convert array or Map type to JSON string value = JSON.stringify(value); } sys.localStorage.setItem(key, value); } // read data public getItem(key: string, defaultValue: any = ""): any {<!-- --> let value = sys.localStorage.getItem(key); // If data acquisition fails, just use the default settings. if (value === null) {<!-- --> return defaultValue; } // Check whether it is a JSON string const regex = /^\s*{[\s\S]*}\s*$/; if (regex.test(value)) {<!-- --> return JSON.parse(value); } return value; } }
Test case:
private debugStorage() {<!-- --> let storageManager = StorageManager.instance; // Check data validity storageManager.setItem("Storage_Debug_1", null); storageManager.setItem("Storage_Debug_2", undefined); storageManager.setItem("Storage_Debug_3", NaN); //storage storageManager.setItem("Storage_Int", 10); storageManager.setItem("Storage_Boolean", true); storageManager.setItem("Storage_Array1", [1,2,3]); storageManager.setItem("Storage_Array2", new Array(4,5,6)); storageManager.setItem("Storage_Map", {<!-- -->name: "TypeScript", index:10}); // retrieve data console.log("Storage_Int", storageManager.getItem("Storage_Int")); console.log("Storage_Boolean", storageManager.getItem("Storage_Boolean")); console.log("Storage_Array1", storageManager.getItem("Storage_Array1")); console.log("Storage_Array2", storageManager.getItem("Storage_Array2")); console.log("Storage_Map", storageManager.getItem("Storage_Map")); }
As for the implementation of removeItem, key, clear
, etc., just call the relevant methods of localStorage
directly.
Expansion 1: Support saving multiple copies of data
In actual project development, frequent functional testing may require us to save multiple copies of local storage data.
You can store the data of different users by using the key key + the player’s unique identifier ID to save multiple copies.
A rough modification to the StorageManager
class can be as follows:
//Initialize the role ID, which can be set after the project successfully obtains user data. private _roleId: string = ""; public setRoleId(id: string) {<!-- --> this._roleId = id; } //Add new method private getNewKey(key: string) {<!-- --> let newKey = key; if (this._roleId.length <= 0) {<!-- --> newKey = `${<!-- -->key}_${<!-- -->this._roleId}`; } return newKey; } // Just call getNewKey in the interface of setItem or getItem
Use ${key}_${this._roleId}
to construct the key to avoid data overwriting caused by repeated key.
Expansion 2: Data Security
Although cocosCreator uses sqlite database for storage, the data exists in clear text, which is not conducive to the security of the project.
Therefore, it is necessary for the project to use encryption algorithms to encrypt plain text content. You need to refer to the blog: cocosCreator’s crypto-es data encryption
Using encryption algorithms to process locally stored data can be done as follows:
- Use
MD5
encryption for key AES encryption
is performed on the stored data of value, andAES decryption
is performed when obtaining the data.
That is, before saving or retrieving data, key and value are encrypted, and the final code is implemented:
import {<!-- --> _decorator, sys} from 'cc'; const {<!-- --> ccclass, property } = _decorator; import CryptoES from "crypto-es"; import {<!-- --> EncryptUtil } from './EncryptUtil'; export class StorageManager {<!-- --> private static _instance: StorageManager = null; private _secretKey: string = ""; private _roleId: string = ""; static get instance() {<!-- --> if (this._instance) {<!-- --> return this._instance; } this._instance = new StorageManager(); this._instance.init(); return this._instance; } private init() {<!-- --> EncryptUtil.initCrypto("key", "vi"); } //Set role ID public setRoleId(id: string) {<!-- --> this._roleId = id; } private getNewKey(key: string) {<!-- --> let newKey = key; if (this._roleId.length <= 0) {<!-- --> newKey = `${<!-- -->key}_${<!-- -->this._roleId}`; } return EncryptUtil.md5(newKey); } // save data public setItem(key: string, value:any) {<!-- --> if (value === undefined || value === null) {<!-- --> console.log(`Local storage data is illegal, key:${<!-- -->key}`); return; } let valueType = typeof(value); if (valueType === "number" & amp; & amp; isNaN(value)) {<!-- --> console.log(`Local storage data is NaN, key:${<!-- -->key}`); return; } if (valueType === "number") {<!-- --> value = value.toString(); } else if (valueType === "boolean") {<!-- --> value = value ? "1" : "0"; } else if (valueType === "object") {<!-- --> value = JSON.stringify(value); } // Encrypt data let newKey = this.getNewKey(key); let newValue = EncryptUtil.aesEncrypt(value); sys.localStorage.setItem(newKey, newValue); } // read data public getItem(key: string, defaultValue: any = ""): any {<!-- --> let newKey = this.getNewKey(key); let value = sys.localStorage.getItem(newKey); // If data acquisition fails, use the default settings. if (value === null) {<!-- --> return defaultValue; } // Decrypt data let newValue = EncryptUtil.aesDecrypt(value); // Check whether it is a JSON string const regex = /^\s*{[\s\S]*}\s*$/; if (regex.test(value)) {<!-- --> return JSON.parse(newValue); } return value; } }
At this point, all the content has been described. I wish everyone a happy study and life!