Android开发笔记(九)
- 单线程处理多模块逻辑
最近工作其中的一个重要任务,在组网层(?)之上实现任务层逻辑,目前功能包括文件传输与历史记录。为了模块具有良好的独立性与复用性,除了设计一套良好的对外接口外(在此不讨论接口问题,参见~~),还需考虑合适的线程模型--采用单线程。其实单线程模型设计很简单,关键在于:
1. 管理单线程的生命周期,并维护其线程队列。
2. 调用接口,内部逻辑第一件事做的是转至内部处理线程即投递消息到线程队列。
在线程循环处理消息的前提下,线程队列将消息继续投递至对应的子模块消息处理。
3. 内部子模块必须实现独立的消息处理函数,子模块间的消息互相不影响。
Java(android)SDK就提供了如下说明的线程解决方案而且很完善,正所谓得天独厚啊。相关的类型及说明如下:
HandleThread或Thread,即Java提供的线程,主要就是创建线程对象,start线程,退出线程。
Looper,即线程的消息队列,每一个线程对应一个消息队列,一对一关系。退出线程是由Looper.quit退出线程消息循环替代完成的。
Handler,即关联至对应线程队列的消息处理类型。一个消息队列允许对应有多个消息处理对象,一对多的关系。
- 随机读写文件
刚开始做Android开发时,遇到读写文件情况都是顺序的读写,所以使用InputStream或OutputStream可以简单顺利搞定。但后来遇到其他复杂的应用场景,可能不是顺序的读写即随机读写。
1. 首先尝试使用java.io.RandomAccessFile,简单使用示例如下:
文件输入输出流:mFileOutput = new RandomAccessFile(new File(uri.getPath()), "rwd");//允许读写,追加写入。
文件输入流:mFileInput = new RandomAccessFile(new File(uri.getPath()), "r");
设置偏移位置:mFileOutput.seek(mFileOutput.length());
读取文件(流)大小:mFileInput.length();
读取数据:readLen += mFileInput.read(buffer, 0, bufferLen);
写入数据:mFileOutput.write(data); mFileOutput.flush();
关闭文件流:mFileInput.close();
注意:如上创建随机读写文件流,均以android.net.Uri对象打开文件,下同。
关于RandomAccessFile类型的具体介绍参见:android-sdk-RandomAccessFile,jdk-RandomAccessFile。
很遗憾的是,我在Android设备上尝试使用RandomAccessFile随机读写文件未成功,在创建对象时候失败抛异常:
网上搜索很多办法犹未解决问题,由于项目时间逼近所以暂留下问题在此笔记,容后续再考虑。你有什么办法吗?
2. 接着,找了另一种办法实现了随机读写文件这样的功能,使用java.nio.channels.FileChannel 对象操作文件读写。使用FileChannel的简单示例如下:
输入流FileChannel:
ContentResolver cr = context.getContentResolver();
mInputStream = (FileInputStream) cr.openInputStream(uri);
mFileInput = mInputStream.getChannel();
mInputStream = (FileInputStream) cr.openInputStream(uri);
mFileInput = mInputStream.getChannel();
输出流FileChannel:
ContentResolver cr = context.getContentResolver();
FileOutputStream fileOutputStream = (FileOutputStream) cr.openOutputStream(uri);
mFileOutput = fileOutputStream.getChannel();
FileOutputStream fileOutputStream = (FileOutputStream) cr.openOutputStream(uri);
mFileOutput = fileOutputStream.getChannel();
设置偏移位置:mFileInput.position(beginPos); 或mFileOutput.position(mFileOutput.size());
读取数据:mFileInput.read(buffer);// buffer is ByteBuffer object.
写入数据:writtenLen += mFileOutput.write(ByteBuffer.wrap(data, 0, data.length));
关闭Channel:mFileInput.close(); 或关闭对应的文件流:mInputStream.close();
这次就成功地以Uri对象打开了(输入输出)文件流,得到对应的FileChannel对象,并顺利地实现了随机读写文件流功能。
相比RandomAccessFile,虽然FileChannel这种办法看起来粗略一些,更多的是需自己设计,但其效率未必较差:D 。
关于FileChannel类型的具体介绍参见:android-sdk-FileChannel,jdk-FileChannel。
btw. android文件路径如:(/mnt/sdcard/...),Uri内容如:(content://media/external/...)。
- 对象序列化类型
可以先尝试回答一个问题,在Java编程什么时候需要用到对象序列化?
1. 通过序列化保存对象,如保存至磁盘。
2. 通过序列化传输对象。如网络传输,进程间传输等。
在Java编程做Android开发过程中,对象序列化可以使用到的主要类型包括: Parcel、Parcelable、Serializable。
1.在使用内存的时候,Parcelable 类比Serializable性能高,所以推荐使用Parcelable类。2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。3.Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点, 也不提倡用,但在这种情况下,还是建议你用Serializable 。
在Android系统中,定位为针对内存受限的设备,因此对性能要求更高,另外系统中采用了新的IPC(进程间通信)机制,必然要求使用性能更出色的对象传输方式。在这样的环境下,Parcel被设计出来,其定位就是轻量级的高效的对象序列化和反序列化机制。Parcel的实现过程是这样的:
1. 整个读写全是在内存中进行,主要是通过malloc()、realloc()、memcpy()等内存操作进行,所以效率比JAVA序列化中使用外部存储器会高很多;
2. 读写时是4字节对齐的,可以看到#define PAD_SIZE(s) (((s)+3)&~3)这句宏定义就是在做这件事情;
3. 如果预分配的空间不够时newSize = ((mDataSize+len)*3)/2;会一次多分配50%;
4. 对于普通数据,使用的是mData内存地址,对于IBinder类型的数据以及FileDescriptor使用的是mObjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的,目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。
Parcel、Parcelable简单的使用,如下:
Parcelable praceable = intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
NetworkInfo otherNetworkInfo = null;
if (praceable != null) {
final Parcel pracel = Parcel. obtain();
praceable.writeToParcel(pracel, 0);
otherNetworkInfo = (NetworkInfo) pracel.readValue(NetworkInfo. class.getClassLoader());
}
注意:Serializable的实现,只需要继承(implements) Serializable即可。这只是给对象打了一个标记,系统会自动将其序列化。
关于Parcel、Parcelable、Serializable三者的详细说明参见:android-sdk-Parcel,android-sdk-Parcelable,android-sdk-Serializable。
(完)
没有评论:
发表评论