数据内存不足

内存不足  时间:2021-01-16  阅读:()

概述回顾上一次分享,我们已经对Android的整体发展,体系结构,工程搭建,界面布局,组件注册,生命周期有了一定认识,接下来研究一下在Android中数据如何存储,如何将数据和界面控件绑定.
Android中的本地存储主要有三种方式:SharePreference:key-value形式,主用于数据较少的配置信息的存储.
SQLite:一些比较复杂的数据结构,特别适合对象存储.
FileSave:比较大的文件(例如日志,图片缓存,apk包等)或者某些特殊的配置文件.
另外还有ContentProvider用于进程间数据共享,不是很常用,下面会简要叙述其原理和FileProvider的使用.
上面描述的都是数据持久化的方式,在某些特殊情况下我们可能需要用到内存缓存,使用一些弱(WeakReference)软(SoftReference)引用技术和LruCache内存缓存类来实现,这里不做讨论.
本次分享的所有主题都有对应的示例代码,可以在这里下载:https://github.
com/lxqxsyu/InnerShareCode2SharePreference创建SharePreference对象:上面的第一个参数是配置文件名称,第二个参数是存储模式,有下面几种:MODE_PRIVATE,则该配置文件只能被自己的应用程序访问.
MODE_WORLD_READABLE,则该配置文件除了自己访问外还可以被其它应该程序读取.

MODE_WORLD_WRITEABLE,则该配置文件除了自己访问外还可以被其它应该程序读取和写入.

MODE_APPEND,检查文件是否存在,存在就往文件追加内容,否则就创建新文件.

存储数据:存储数据需要获得Editor对象,然后使用Editor对象添加对应格式数据,最后记得commit(同步)或者apply(异步).
支持的数据类型如下:获取数据:根据对应的key获取值,后面第二个参数是默认值(当key没找到时).
privatestaticfinalStringSP_TEST="sp_test";privateSharedPreferencesmSharedPreference;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.
onCreate(savedInstanceState);setContentView(R.
layout.
activity_test_sharepreference);mSharedPreference=getSharedPreferences(SP_TEST,Context.
MODE_PRIVATE);}privatevoidsave(Stringkey,Stringdata){SharedPreferences.
Editoreditor=mSharedPreference.
edit();editor.
putString(key,data);editor.
commit();//或者editor.
apply();}privateStringget(Stringkey){returnmSharedPreference.
getString(key,null);}本质上SharePreference是基于xml格式的一种文件存储,可以在data/{packagename}/shared_prefs下找到该文件:SQLite数据库Android中的数据库是SQLite3,可以直接使用adb命令来操作该数据,例如:创建一个名为test.
db的数据库:创建一个表:插入数据:Android中Google为我们提供了一个方便操作数据库的类SQLiteOpenHelper,我们可以重写它来实现对应的增删改查操作.
首先定义一个表结构,注意需要实现接口BaseColumns:dfdfdfdfdfdgdgdgsqlite>sqlite3test.
dbsqlite>createtablemytable(idintegerprimarykey,titletext,subtitletext);sqlite>insertintomytable(id,value)values(1,'Micheal','SubMicheal');sqlite>insertintomytable(id,value)values(2,'Jenny','SubJenny');sqlite>insertintomytable(value)values('Francis','SubFrancis');sqlite>insertintomytable(value)values('Kerk','SubKerk');publicstaticclassMyTableEntryimplementsBaseColumns{publicstaticfinalStringTABLE_NAME="mytable"表名称publicstaticfinalStringCOLUMN_NAME_TITLE="title";//字段一publicstaticfinalStringCOLUMN_NAME_SUBTITLE="subtitle";//字段二publicMyTableEntry(Stringtitle,Stringsubtitle){this.
title=title;this.
subtitle=subtitle;}publicStringtitle;接下来,创建一个TestDBHelper类继承自SQLiteOpenHelper.
构造函数:context:上下文环境DATABASE_NAME:数据库名factory:用来创建对象的Cursor,一般为nullDATABASE_VERSION:数据库版本号定义辅助SQL:重写SQLiteOpenHelper的onCreate,onUpdate(),onDowngrade()方法:publicStringsubtitle;}publicclassTestDBHelperextendsSQLiteOpenHelper{publicstaticfinalStringDATABASE_NAME="test.
db";publicstaticfinalintDATABASE_VERSION=1;publicTestDBHelper(Contextcontext){super(context,DATABASE_NAME,null,DATABASE_VERSION);}}privatestaticfinalStringSQL_CREATE_ENTRIES="CREATETABLE"+MyTableEntry.
TABLE_NAME+"("+MyTableEntry.
_ID+"INTEGERPRIMARYKEY,"+MyTableEntry.
COLUMN_NAME_TITLE+"TEXT,"+MyTableEntry.
COLUMN_NAME_SUBTITLE+"TEXT)";privatestaticfinalStringSQL_DELETE_ENTRIES="DROPTABLEIFEXISTS"+MyTableEntry.
TABLE_NAME;@OverridepublicvoidonCreate(SQLiteDatabasedb){db.
execSQL(SQL_CREATE_ENTRIES);}@OverridepublicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){db.
execSQL(SQL_DELETE_ENTRIES);onCreate(db);}@OverridepublicvoidonDowngrade(SQLiteDatabasedb,intoldVersion,intnewVersion){onUpgrade(db,oldVersion,newVersion);}onCreate():该方法是第一个被调用的,一般在其中初始化创建数据库,也就是说数据库的增删改查操作必须在这个方法执行完之后进行.
onUpdate():该方法是当数据库版本发生变化(版本增加)的时候(也就是数据库结构变化后)执行的方法,一般需要在这里重新初始化数据库.
onDowngrade():该方法是当数据库版本降级(版本减少)的时候执行.
接下来我们来使用上面的TestDBHelper实现数据库增加和查询:publicclassSQLiteTestActivityextendsBaseActivity{privateTestDBHelpermDBHelper;@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){super.
onCreate(savedInstanceState);mDBHelper=newTestDBHelper(this);}//增加数据privatevoidsaveData(MyTableEntryentry){SQLiteDatabasedb=mDBHelper.
getWritableDatabase();ContentValuesvalues=newContentValues();values.
put(MyTableEntry.
COLUMN_NAME_TITLE,entry.
title);values.
put(MyTableEntry.
COLUMN_NAME_SUBTITLE,entry.
subtitle);longnewRowId=db.
insert(MyTableEntry.
TABLE_NAME,null,values);}//根据title查询数据privateListgetData(Stringtitle){SQLiteDatabasedb=mDBHelper.
getReadableDatabase();String[]projection={BaseColumns.
_ID,MyTableEntry.
COLUMN_NAME_TITLE,MyTableEntry.
COLUMN_NAME_SUBTITLE};Stringselection=MyTableEntry.
COLUMN_NAME_TITLEString[]selectionArgs={title};StringsortOrder=MyTableEntry.
COLUMN_NAME_SUBTITLE+"DESC";Cursorcursor=db.
query(MyTableEntry.
TABLE_NAME,//Thetabletoqueryprojection,Thearrayofcolumnstoreturn(passnulltogetall)selection,ThecolumnsfortheWHEREclauseselectionArgs,ThevaluesfortheWHEREclausenull,don'tgrouptherowsnull,don'tfilterbyrowgroupssortOrderThesortorder);数据库框架通过上面的体验,是不是感觉操作数据库还是挺麻烦的,如果有多张表工作量看起来还是蛮大的,比较好的是我们可以使用一些第三方封装的对象映射框架来直接通过操作对象来操作数据库.

Android中数据库对象映射框架很多,例如:LitePal,Room,Realm,ObjectBox等,下面我们就来看一下最新的ObjectBox框架的使用.
第一步,引入库打开工程根目录下的build.
gradle文件,添加如下配置:接着在app/build.
gradle的dependencies{}中添加如下代码:最后在app/build.
gradle最后一行添加如下插件使用代码:第二步,定义映射类ListitemIds=newArrayList();while(cursor.
moveToNext()){MyTableEntryentry=newMyTableEntry();longitemId=cursor.
getLong(cursor.
getColumnIndexOrThrow(MyTableEntry.
_ID));entry.
title=cursor.
getString(cursor.
getColumnIndexOrThrow(MyTableEntry.
COLUMN_NAME_TITLE));entry.
subtitle=cursor.
getString(cursor.
getColumnIndexOrThrow(MyTableEntry.
COLUMN_NAME_SUBTITLE));itemIds.
add(entry);}cursor.
close();returnitemIds;}}buildscript{ext.
objectboxVersion='2.
1.
0'dependencies{classpath"io.
objectbox:objectbox-gradle-plugin:$objectboxVersion"}}dependencies{debugImplementation"io.
objectbox:objectbox-android-objectbrowser:$objectboxVersion"releaseImplementation"io.
objectbox:objectbox-android:$objectboxVersion"}applyplugin:'io.
objectbox'ObjectBox同主流的DB库一样,采用注解来标注实体类,然后编译生成DAO相关类.
使用@Entity标注需要存取的实体类.
@Id标记用于ObjectBox进行ID自增.
第三步,获取BoxStore对象在Application中创建BoxStore并向外暴露对应的MyBoxTable的Box对象,后面可以使用该对象操作数据库.
第四步,操作数据库增加数据:查询数据:文件存储@EntitypublicclassMyBoxTable{@Idpubliclongid;publicStringtitle;publicStringsubtitle;}publicclassAppextendsApplication{privatestaticAppmInstance;privateBoxStoremBoxStore;@OverridepublicvoidonCreate(){super.
onCreate();mInstance=this;mBoxStore=MyObjectBox.
builder().
androidContext(this).
build();}publicstaticAppgetInstance(){returnmInstance;}publicBoxgetMyTableBox(){returnmBoxStore.
boxFor(MyBoxTable.
class);}}App.
getInstance().
getMyTableBox().
put(newMyBoxTable(title,subtitle));Listentrys=App.
getInstance().
getMyTableBox().
query().
equal(MyBoxTable_.
title,key).
build().
find();Android系统自带存储空间,也可以通过sd卡来扩展存储,它们之间的关系就好比电脑的硬盘和移动硬盘的关系.
前者空间较小,后者空间大,但后者不一定可用.
开发应用,处理本地数据存取时,可能会遇到这些问题:1.
需要判断sd卡是否可用:占用过多机身内部存储,容易招致用户反感,优先将数据存放于sd卡.
2.
应用数据存放路径,同其他应用应该保持一致,应用卸载时,清除数据,不然会招致用户反感.

3.
需要判断两者的可用空间:sd卡存在时,可用空间反而小于机身内部存储,这时应该选用机身存储.
4.
数据安全性,本应用数据不愿意被其他应用读写.
5.
图片缓存等,不应该被扫描加入到用户相册等媒体库中去.
内部存储方式存储的文件属于其所创建的应用程序私有,其他应用程序无权进行操作.
当创建的应用程序被卸载时,其内部存储的文件也随之被删除.
当内部存储器的存储空间不足时,缓存文件可能会被删除以释放空间.
因此,缓存文件是不可靠的.
当使用缓存文件时,自己应该维护好缓存文件,并且将缓存文件限制在特定大小之内.

第一步:通过Context.
openFileOutput(Stringname,intmode)方法打开文件并设定读写方式,返回FileOutputStream.
其中,参数mode取值为:MODE_PRIVATE:默认访问方式,文件仅能被创建应用程序访问.
MODE_APPEND:若文件已经存在,则在文件末尾继续写入数据,而不抹掉文件原有内容.

MODE_WORLD_READABLE:允许该文件被其他应用程序执行读取内容操作.
MODE_WORLD_WRITEABLE:允许该文件被其他应用程序执行写操作.
第二步:通过Context.
openFileInput(Stringname)方法读取文件内容:privatevoidwriteFile(Stringmessage){try{FileOutputStreamfos=openFileOutput(FILE_NAME,Context.
MODE_PRIVATE);fos.
write(message.
getBytes());fos.
close();}catch(FileNotFoundExceptione){e.
printStackTrace();}catch(IOExceptione){e.
printStackTrace();}}privateStringreadFile(){ByteArrayOutputStreambaos=newByteArrayOutputStream();try{FileInputStreamfis=openFileInput(FILE_NAME);byte[]buffer=newbyte[1024];intlength;while((length=fis.
read(buffer))!
=-1){baos.
write(buffer,0,length);}returnbaos.
toString("UTF-8");}catch(FileNotFoundExceptione){e.
printStackTrace();}catch(IOExceptione){e.
printStackTrace();}returnnull;我们上面设置的路径FILE_NAME如果是这样定义的:则文件会存放在data/{packagename}/files/目录下:如果我们要存储到sd卡就需要先判断sd卡是否可用,然后再进行存储:在API19/Andorid4.
4/KITKAT之前读写sd卡是需要主动声明下面两个权限的:路径规律:一般地,通过Context和Environment相关的方法获取文件存取的路径.
通过这两个类可获取各种路径,如下:}publicstaticfinalStringFILE_NAME="test_write_read.
txt";publicstaticbooleanhasSDCardMounted(){Stringstate=Environment.
getExternalStorageState();if(state!
=null&&state.
equals(Environment.
MEDIA_MOUNTED)){returntrue;}else{returnfalse;}}($rootDir)+-/dataEnvironment.
getDataDirectory()||appDataDir)|+-data/com.
myappfilesDir)filesContext.
getFilesDir()/Context.
getFileStreamPath("")file1->Context.
getFileStreamPath("file1")根目录:内部(相对apk)存储目录:/data/{packagename}和外部存储目录/storage/sdcard0/Android/data/{packagename}下存储的数据在apk卸载后会被系统删除,我们应将应用的数据放于这两个目录中.
注意上面getFilesDir()和getExternalFilesDir()所对应的内部和外部存储的区别,机身内存不足时,文件会被删除,外部存储没有实时监控,当空间不足时,文件不会实时被删除,可能返回空对象.

外部存储公开目录:cacheDir)cacheContext.
getCacheDir()app_$nameContext.
getDir(Stringname,intmode)||($rootDir)+-/storage/sdcard0->Environment.
getExternalStorageDirectory()Environment.
getExternalStoragePublicDirectory("")|+-dir1Environment.
getExternalStoragePublicDirectory("dir1")||($appDataDir)+-Andorid/data/com.
myapp|filesDir)filesContext.
getExternalFilesDir("")file1->Context.
getExternalFilesDir("file1")Music->Context.
getExternalFilesDir(Environment.
Music);Picture->.
.
.
Environment.
Picture|cacheDir)cacheContext.
getExternalCacheDir()|Environment.
getDataDirectory():/dataEnvironment.
getExternalStorageDirectory():/storage/sdcard0//内部Context.
getFilesDir()/Context.
getFileStreamPath("")Context.
getCacheDir()Context.
getDir(Stringname,intmode)//外部Context.
getExternalFilesDir()Context.
getExternalCacheDir()外部存储中,公开的数据目录.
这些目录将不会随着应用的删除而被系统删除,请斟酌使用.
FileProvider上面文件的存储目录如果我们选用类似getCacheDir()这种只可以apk内部访问,如果我们要暴露给外部可以使用类似Environment.
getExternalStorageDirectory()完全暴露给了外部所有apk,这种显然是不够安全的,有的时候我们只需要暴露给特定的apk,这个时候就需要用到FileProvider来实现了.
FileProvider是ContentProvider的一个特殊子类,它通过创建一个content://Uri来使应用程序相关的文件实现安全共享:file:///Uri.
ContentProvider提供了对底层数据存储方式的抽象,如下图所示如果我们将SQLite换成MongoDB对于上层apk而言没有什么变化.
但是最主要的特点还是ContentProvider为应用间的数据交互提供了一个安全的环境,它准许你把自己的应用数据根据需求开放给其他应用进行增、删、改、查,而不用担心直接开放数据库权限而带来的安全问题.

Environment.
getExternalStorageDirectory():/storage/sdcard0Environment.
getExternalStoragePublicDirectory(""):/storage/sdcard0Environment.
getExternalStoragePublicDirectory("folder1"):/storage/sdcard0/folder1使用FileProvider的大致步骤如下(了解即可):第一步:在manifest清单文件中注册provider第二步:指定共享的目录,在资源(res)目录下创建一个xml目录,然后创建一个名为file_paths的资源文件.
这里的有很多形式,对应的是Environment.
getExternalStorageDirectory()目录.
这个name名字可以随便起,替代path的一个显式称谓,这个值能起到隐藏共享目录的名称的作用.
这个path是要共享的子目录路径,这个值只能是一个目录路径而不能是一个文件的路径.

RFCHOST - 洛杉矶CN2 GIA VPS季付23.9美元起 100Mbps带宽

RFCHOST,这个服务商我们可能有一些朋友知道的。不要看官网是英文就以为是老外服务商,实际上这个服务商公司在上海。我们实际上看到的很多商家,有的是繁体,有的是英文,实际上很多都是我们国人朋友做的,有的甚至还做好几个品牌域名,实际上都是一个公司。对于RFCHOST商家还是第一次分享他们家的信息,公司成立大约2015年左右。目前RFCHOST洛杉矶机房VPS正进行优惠促销,采用CN2优化线路,电信双...

virmach:AMD平台小鸡,赌一把,单车变摩托?$7.2/年-512M内存/1核/10gSSD/1T流量,多机房可选

virmach送来了夏季促销,价格低到爆炸,而且在低价的基础上还搞首年8折,也就是说VPS低至7.2美元/年。不过,这里有一点要说明:你所购买的当前的VPS将会在09/30/2021 ~ 04/30/2022进行服务器转移,而且IP还会改变,当前的Intel平台会换成AMD平台,机房也会变动(目前来看以后会从colocrossing切换到INAP和Psychz),采取的是就近原则,原来的水牛城可能...

ucloud香港服务器优惠活动:香港2核4G云服务器低至358元/年,968元/3年

ucloud香港服务器优惠降价活动开始了!此前,ucloud官方全球云大促活动的香港云服务器一度上涨至2核4G配置752元/年,2031元/3年。让很多想购买ucloud香港云服务器的新用户望而却步!不过,目前,ucloud官方下调了香港服务器价格,此前2核4G香港云服务器752元/年,现在降至358元/年,968元/3年,价格降了快一半了!UCloud活动路子和阿里云、腾讯云不同,活动一步到位,...

内存不足为你推荐
域名注册域名注册是什么意思,域名注册有什么用处美国虚拟空间国内虚拟空间与美国虚拟主机有什么不一样.net虚拟主机想买个同时支持php和.net的虚拟主机,哪里可以买到这样的空间?是同时支持的那种。独立ip主机独立IP虚拟主机是什么?有哪些优势?海外域名怎么挑选合适的国外域名?网站空间购买在哪里购买网站空间深圳网站空间深圳网站设计 哪家好一些?虚拟主机评测网哪里有可靠的免费虚拟主机虚拟主机评测浅谈建站新手如何挑选虚拟主机天津虚拟主机天津哪个是新网互联代理呢,我打算购买邮局?
虚拟主机试用30天 阿云浏览器 wdcp 免费ddos防火墙 中国特价网 租空间 gg广告 dux 河南移动邮件系统 bgp双线 域名转接 nerds 南通服务器 vip购优惠 umax120 如何安装服务器系统 上海联通宽带测速 免费ftp 华为云建站 工信部网站备案查询 更多