概述回顾上一次分享,我们已经对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是要共享的子目录路径,这个值只能是一个目录路径而不能是一个文件的路径.
官方网站:点击访问亚洲云官网618活动方案:618特价活动(6.18-6.30)全站首月活动月底结束!地区:浙江高防BGPCPU:至强铂金8270主频7 默频3.61 睿频4.0核心:8核(最高支持64核)内存:8G(最高支持128G)DDR4 3200硬盘:40G系统盘+80G数据盘带宽:上行:20Mbps/下行:1000Mbps防御:100G(可加至300G)防火墙:提供自助 天机盾+金盾 管...
iWebFusion(iWFHosting)在部落分享过很多次了,这是成立于2001年的老牌国外主机商H4Y旗下站点,提供的产品包括虚拟主机、VPS和独立服务器租用等等,其中VPS主机基于KVM架构,数据中心可选美国洛杉矶、北卡、本德、蒙蒂塞洛等。商家独立服务器可选5个不同机房,最低每月57美元起,而大流量10Gbps带宽服务器也仅149美元起。首先我们分享几款常规服务器配置信息,以下机器可选择5...
百驰云成立于2017年,是一家新国人IDC商家,且正规持证IDC/ISP/CDN,商家主要提供数据中心基础服务、互联网业务解决方案,及专属服务器租用、云服务器、云虚拟主机、专属服务器托管、带宽租用等产品和服务。百驰云提供源自大陆、香港、韩国和美国等地骨干级机房优质资源,包括BGP国际多线网络,CN2点对点直连带宽以及国际顶尖品牌硬件。专注为个人开发者用户,中小型,大型企业用户提供一站式核心网络云端...