看到CSDN的征文活动“移动开发那点事”想起自己也做过几次移动项目,特此更文~
本科课程项目索引:http://blog.csdn.net/xiaowei_cqu/article/details/7747205
ido是我除课程设计外第一个完整的项目,现在看来很简单,但对当时大二的我来说感觉是很“大”的项目。项目起因是参加学校组织的软件设计比赛,战线略微有点长,过程也挺纠结的,中间几次都有犹豫“要不要做下去”的问题——主要因为不够自信,毕竟第一次参赛而且队伍里全是女生。幸运是最终还是坚持下来了,也一定程度成了我大学生活的分水岭。至少从那之后,心态好了很多。再次感谢最最靠谱儿的小鹿,当然还有翠翠,董姐。
先晒个视频~
ido手机阅读器本软件几乎提供了手机阅读需要的所有功能,支持txt,doc等各种格式文本阅读,海量在线书城搜索下载,特效翻页,文本百分比跳转,字体无级缩放,颜色背景自定义设置,自动标签记录,快捷键设置,以及本地图书文本分类管理等。此外,本软件还提供了许多极为人性化的功能,包括最后阅读记录,切换阅读文本,显示系统时间,字典查询等,让用户尽享豪华读书体验!同时在界面设计上本软件也充分为用户考虑,简洁明了易于操作,并支持用户个人风格设置。
- 阅读及文本处理:.txt格式文本阅读、.txt格式文本处理、文本跳转、成熟电子格式(.doc等)阅读
- 图书管理:本地图书管理、本地图书分类、书签管理、阅读文本切换
- 在线图书下载:在线书城连接、海量图书下载、下载图书分类管理
- 人性化拓展功能:快捷键设置、显示系统时间、备忘录、蓝牙传送
- 使用帮助:dodo帮助、dodo知道
详细设计
- 表示层提供项目的操作界面,用作隔离层,将用户界面与业务功能的实现分开;
- 业务逻辑层包含各种业务规则和逻辑的实现;
- 数据访问层包括数据实体并提供对数据实体操作的服务。
类图:
命名规则:
类名:“资源名Manager”,如BookManager类管理图书(Book)的类;以大写字母开头,包含多个单词的类名,所有单词连接在一起,每个单词首字母大写属性:小写字母开始,第二个单词开始首字母大写
方法:首字母大写,多个单词租出的方法每个单词首字母大写
业务层用以完成程序的内部逻辑;业务规则及逻辑全部封装到类中以类方法的形式实现。
类图:
命名规则:
类名:“资源名Manager”,如BookManager类管理图书(Book)的类;以大写字母开头,包含多个单词的类名,所有单词连接在一起,每个单词首字母大写
属性:小写字母开始,第二个单词开始首字母大写
方法:首字母大写,多个单词租出的方法每个单词首字母大写
窗体导航图:
命名规则:
窗体名:“Form资源名”,如FormBooks为“图书管理”模块打开的窗体。所有单词首字母大写,子模块打开的窗体为区分加The。如阅读时打开书签为FormTheBookmarks
2010.4.22~2010.5.22 (五次例会)
选择windows moblie平台;学习C#、microsoft “How Can I ”系列;每周例会交流学习进度;模拟用户,收集需求;按模块分工,以实现功能为主
完成V1.1版本
实现阅读、字体设置等基本功能,实现背景更换、显示时间等,实现“网上书城”、手机红外线发送;完成《需求规格说明书》、《第一阶段报告》
概要设计,完成基本功能点;规范文档;幸运通过初赛
完成V1.2版本
实现添加书签、Word文件阅读规定功能;改进“网上书城”模块;增加RSS订阅;编写《概要设计说明书》《周例会纪要》暑假,实验室;请教老师,学长;从“手机项目”角度出发,增添更多功能
完成V2.1版本
黑盒测试,软件升级;小范围发布,收集反馈信息;总结报告
完成V2.2版本
再次迭代,优化代码;调整页面,增加多种皮肤,完善帮助说明;完成《用户使用手册》《第三阶段报告》继续《周例会纪要》很忙,废话不多说。
将mic录音和伴奏混合成wav。
public class MixRunnable implements Runnable {
private MixRecorder context;
/**
* AudioRecord创建参数类
*
* @author christ
*/
private static class RecorderParameter {
// 音频获取源
private static int audioSource = MediaRecorder.AudioSource.MIC;
// 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
private static final int sampleRateInHz = 44100;
// 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
private static final int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
// 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
// 缓冲区字节大小
private static int bufferSizeInBytes;
}
// 设置运行状态
private boolean isRunning = true;
// AudioRecord对象
private static AudioRecord recorder;
// 设置MediaPlayer对象
private static MediaPlayer mediaPlayer;
// 伴奏文件
private FileInputStream accompany;
// 原唱文件
private FileInputStream original;
// 得分
private int score;
private boolean isFirst = true;
/**
* 混音评分线程的构造方法
*
* @param accompany
* :伴奏文件路径
* @param original
* :原唱文件路径
* @throws FileNotFoundException
*/
public MixRunnable(MixRecorder context, String accompany, String original) throws FileNotFoundException {
this.context = context;
this.accompany = new FileInputStream(accompany);
this.original = new FileInputStream(original);
creatAudioRecord();
mediaPlayer = new MediaPlayer();
}
@Override
public void run() {
try {
// MediaPlayer准备
mediaPlayer.reset();
mediaPlayer.setDataSource("/sdcard/111.wav");
// mediaPlayer.setDataSource(accompany.getFD());
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
isRunning = false;
}
});
mediaPlayer.prepare();
// 跳过头
accompany.read(new byte[44]);
original.read(new byte[44]);
FileOutputStream fos = new FileOutputStream(new File("/sdcard/love.raw"));
// 开始读
byte[] sourceReader = new byte[RecorderParameter.bufferSizeInBytes * 2];
short[] sourceShortArray;
short[] audioReader = new short[sourceReader.length / 4];
mediaPlayer.start();
recorder.startRecording();
while (isRunning) {
int sourceReadSize = accompany.read(sourceReader, 0, sourceReader.length);
if (sourceReadSize < 0) {
isRunning = false;
continue;
}
sourceShortArray = byteToShortArray(sourceReader, sourceReadSize / 2);
recorder.read(audioReader, 0, audioReader.length);
short[] oneSecond = mixVoice(sourceShortArray, audioReader, sourceReadSize / 2);
byte[] outStream = new byte[oneSecond.length * 2];
for (int i = 0; i < oneSecond.length; i++) {
byte[] b = shortToByteArray(oneSecond<i>);
outStream[2 * i] = b[0];
outStream[2 * i + 1] = b[1];
}
Log.d("mtime4", "" + System.currentTimeMillis());
fos.write(outStream);
// 评分
byte[] srcBuffer = new byte[outStream.length];
original.read(srcBuffer);
int x = score(byteToShortArray(srcBuffer, srcBuffer.length / 2), oneSecond);
System.out.println(x);
}
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
recorder.release();
mediaPlayer.release();
fos.close();
copyWaveFile("/sdcard/love.raw", "/sdcard/haah.wav");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 创建AudioRecord对象方法
*/
private void creatAudioRecord() {
// 获得缓冲区字节大小
RecorderParameter.bufferSizeInBytes = AudioRecord.getMinBufferSize(RecorderParameter.sampleRateInHz,
RecorderParameter.channelConfig, RecorderParameter.audioFormat) * 20;
// 创建AudioRecord对象
recorder = new AudioRecord(RecorderParameter.audioSource, RecorderParameter.sampleRateInHz,
RecorderParameter.channelConfig, RecorderParameter.audioFormat,
RecorderParameter.bufferSizeInBytes);
}
private short[] mixVoice(short[] source, short[] audio, int items) {
short[] array = new short[items];
for (int i = 0; i < items; i++) {
array<i> = (short) ((source<i> + audio[i / 2]) / 2);
}
return array;
}
/**
* byte数组转换成short数组
*
* @param data
* @param items
* @return
*/
private short[] byteToShortArray(byte[] data, int items) {
short[] retVal = new short[items];
for (int i = 0; i < retVal.length; i++)
retVal<i> = (short) ((data[i * 2] & 0xff) | (data[i * 2 + 1] & 0xff) << 8);
return retVal;
}
/**
* short转byte数组
*
* @param s
* @return
*/
private byte[] shortToByteArray(short s) {
byte[] shortBuf = new byte[2];
for (int i = 0; i < 2; i++) {
int offset = (shortBuf.length - 2 + i) * 8;
shortBuf<i> = (byte) ((s >>> offset) & 0xff);
}
return shortBuf;
}
/**
* <a href="/index.html"http://www.eoeandroid.com/home.php?mod=space&uid=7300\"" target="\"_blank\"">@return</a> the recorder
*/
public static AudioRecord getRecorder() {
return recorder;
}
public static MediaPlayer getMediaPlayer() {
return mediaPlayer;
}
/**
* 设置线程运行状态
*
* @param isRunning
*/
public void setIsRunning(boolean isRunning) {
this.isRunning = isRunning;
}
/**
* 获取线程运行状态
*
* @return
*/
public boolean IsRunning() {
return isRunning;
}
public int getScore() {
return score;
}
private void copyWaveFile(String inFilename, String outFilename) {
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = RecorderParameter.sampleRateInHz;
int channels = 2;
long byteRate = 16 * RecorderParameter.sampleRateInHz * channels / 8;
byte[] data = new byte[RecorderParameter.bufferSizeInBytes];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav
* 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有 自己特有的头文件。
*/
private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen,
long longSampleRate, int channels, long byteRate) throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = 16; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
private int score(short[] src, short[] user) {
int srcZ = 0, userZ = 0;
boolean isAsc = false;
boolean uisAsc = false;
for (int i = 1; i < src.length; i++) {
if (isAsc) {
if (src[i - 1] > src<i>) {
isAsc = false;
}
} else {
if (src[i - 1] < src<i>) {
isAsc = true;
srcZ += 1;
}
}
if (uisAsc) {
if (user[i - 1] > user<i>) {
uisAsc = false;
}
} else {
if (user[i - 1] < user<i>) {
uisAsc = true;
userZ += 1;
}
}
}
return Math.abs(srcZ - userZ);
}
}Activity的lanuchMode有四种standard(默认),singleTop,singleTask,singleInstance.
standard:每次都创建一个实例,默认将Activity加入到当前Task。
singleTop:启动的不是当前的Activity的话,则创建一个实例,并加入当前Task,否则抛弃
Intent不做任何反应
singleTask:只有一个Task,不会重新创建已存在的Activity。
singleInstance:一个Task里只有一个Activity。启动Acivity时,会重新创建一个Task,并
把Activity加入新建的Task。
注意:当一个Activity的新实例被创建去处理新Intent时,用户总是可以按返回键返回到之前
的状态(之前的Activity)。但是当一个已存在的Activity实例去处理新的Intent时,用
户不可以返回键返回到Intent到达之前的状态。
清处任务栈:
alwaysRetainTaskState属性: 如果根活动此属性设为true,任务将保留在Task
中。即使离开很长一段时间,也不会被系统清除。
clearTaskOnLaunch属性:如果根活动此属性设为true,只要用户离开就清除根活
动之外的活动。
finishOnTaskLaunch属性: 作用于单个活动。而且它能移除任何活动,包括根活
动。当它被设置为true时,任务本次会话的活动的部
分还存在,如果用户离开并返回到任务,它将不再存在。
用户按下Home键后,再打开一个新的活动。默认重新启动一个Task,若打开一个已开启过
的活动,将跳到对应的Task中的对应栈顶。