目录
3.AVD(Android Virtual Devices)
1. 自定义一个文件写入操作类Filestorage.java
一、实验目的
- 了解Android开发
- 掌握传感器调用
- 交互式界面设计
二、实验要求
1) 搭建开发平台;
2)编写简单程序,实现按钮、屏幕操作的基本功能,应用程序下传安装在手机运行;
3)调用传感器,采集数据;
4)使用屏幕显示功能,实时显示采集的数据;
5)使用屏幕绘图功能,实时绘制采集的传感器数据;
6)使用对话框方式设置存储位置,保存数据;
7)实地测试,数据分析处理(根据实际数据情况,各自选用有效地方法)
三、实验原理
(一)第一次实验
本次实验我们将搭建开发平台,编写简单程序,实现按钮、屏幕操作的基本功能,应用程序下传安装在手机运行。我们需要搭建Android的开发环境并开发一个按键交互小程序。下面我们首先介绍一下Android开发所需要用到的基础框架:JDK和Android SDK开发套件。
1.JDK(Java Development Kit)
JAVA作为一款知名的编程语言,自面世以来一直非常的流行。在最近几年的编程语言排行榜上更是稳坐第一名的宝位。而Android系统在开发之时也选择了JAVA作为其编程语言。JDK的全名叫做Java Development Kit JAVA开发包,从而为我们使用JAVA编程提供了相应的环境以及类库。到目前为止,Java已经发展到了Java V8版,并在不断完善之中。而本次实验使用的Android开发均要使用JDK作为基础的开发环境。
2.Android SDK
Android SDK是一个为Android开发的软件包,它包含了调试器、API函数库、安卓模拟器以及一系列开发文档在内的各种工具。谷歌官方开发出了基于Eclipse的ADT(Android Develop Tools)插件,通过为Eclipse安装插件的方式支持Android的开发,目前官方已经停止ADT插件的开发与维护工作,并开发出了更加强大以及好用的Android Studio软件。本次实验则使用JDK+Android Studio的组合来开发Android APP。
3.AVD(Android Virtual Devices)
AVD就是运行于电脑上的安卓虚拟器,它可以使我们在没有手机的情况下通过电脑测试数据。
这与我们真实运行手机的情况非常相近。这样我们就可以非常方便的在电脑上调试程序。
4.Android 系统平台架构分析
Android 平台由Linux内核、中间件、应用程序平台和应用软件组成,其系统架构如下图所示:
从图中我们可以看到整个安卓系统由Linux核心层、库、安卓运行时、应用程序框架以及应用层构成。而我们一般开发APP都在应用层进行开发。
a) Linux操作系统和驱动(Linux kernel):
由C语言实现。 Android核心系统服务依赖于Linux2.6内核,包括:安全性、内存管理、进程管理、网络协议、驱动模型。Linux内核也作为硬件和软件栈之间的抽象层。除了标准的Linux内核外,Android还增加了内核的驱动程序:Binder(IPC)驱动、显示驱动、输入设备驱动、音频系统驱动、摄像头驱动、WiFi驱动、蓝牙驱动、电源管理。
b) 程序库 (LIBRARIES)
程序库是指可供使用的各种标准程序、子程序、文件以及它们的目录等信息的有序集合,Android包含一些C/C++库,Android系统中不同的组件通过应用程序框架可以使用这些库本地框架是有C/C++实现。包含C/C++库,被Android系统中不同组件使用,它们通过Android应用程序框架为开发者进行服务。
c) Java运行环境(ANDROIDRUNTIME)
Android运行库包括两部分:一是核心库,二是自身的Dalvik虚拟机。
核心库提供Java编程语言核心库的大多数功能。
Dalvik虚拟机是Google专为Android开发的 。每一个Android应用程序都在自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。
d) Android应用框架(ANDROIDFRAMEWORK)
应用程序框架是指定义了一个应用程序运行所必须的全部功能组件,开发者也可以访问核心应用程序所使用的API框架。在Android系统中,开发人员也可以完全访问核心应用程序所使用的API框架。
e) Android应用程序(APPLICATIONS)
Android应用程序都是有Java语言编写的。用户开发的Android应用程序和Android的核心应用程序是同一层次的,它们都是基于Android的系统API构建的。
(二)第二次实验
我们将进行基于Android系统的APP开发实验。而本次实验则是关于手机传感器的应用——加速传感器小程序。下面我们首先介绍一下各个传感器。
1.Android的三大类传感器
Android传感器按大方向划分大致有这么三类传感器:动作(Motion)传感器、环境(Environmental)传感器、位置(Position)传感器。
(1)动作传感器
这类传感器在三个轴(x、y、z)上测量加速度和旋转角度。包括如下几个传感器:
加速(accelerometer)传感器、重力(gravity)传感器、陀螺仪(gyroscope)传感器、旋转向量(rotational vector )传感器。
(2)环境传感器
这类传感器可以测量不同环境的参数,例如,周围环境的空气温度和压强、光照强度和湿度。包括如下几个传感器:
湿度(barometer)传感器、光线(photometer)传感器、温度(thermometer)传感器
(3)位置传感器
这类传感器可以测量设备的物理位置。包括如下几个传感器:
方向(orientation)传感器、磁力(magnetometer)传感器
了解后我们就开始进入传感器的编程工作了,接下来我们看一下Android为我们提供的传感器框架(Android sensor framework,简称ASF)。
2.Android传感器框架
Android SDK为我们提供了ASF,可以用来访问当前Android设备内置的传感器。ASF提供了很多类和接口,帮助我们完成各种与传感器有关的任务。例如:
1)确定当前Android设备内置了哪些传感器。
2)确定某一个传感器的技术指标。
3)获取传感器传回来的数据,以及定义传感器回传数据的精度。
4)注册和注销传感器事件监听器,这些监听器用于监听传感器的变化,通常从传感器回传的数据需要利用这些监听器完成。
ASF允许我们访问很多传感器类型,这些传感器有一些是基于硬件的传感器,还有一些是基于软件的传感器。基于硬件的传感器就是直接以芯片形式嵌入到Android设备中,这些传感器直接从外部环境获取数据。基于软件的传感器并不是实际的硬件芯片,基于软件的传感器传回的数据本质上也来自于基于硬件的传感器,只是这些数据通常会经过二次加工。所以基于软件的传感器也可以称为虚拟(virtual)传感器或合成(synthetic)传感器。
Android对每个设备的传感器都进行了抽象,其中SensorManger类用来控制传感器,Sensor用来描述具体的传感器,SensorEventListener用来监听传感器值的改变。
(1)SensorManager类
用于创建sensor service的实例。该类提供了很多用于访问和枚举传感器,注册和注销传感器监听器的方法。而且还提供了与传感器精度、扫描频率、校正有关的常量。
(2)Sensor类
Sensor类为我们提供了一些用于获取传感器技术参数的方法。如版本、类型、生产商等。例如所有传感器的TYPE类型如下:
|
序号 |
传感器 |
Sensor类中定义的TYPE常量 |
|
1 |
加速度传感器 |
TYPE_ACCELEROMETER |
|
2 |
温度传感器 |
TYPE_AMBIENT_TEMPERATURE |
|
3 |
陀螺仪传感器 |
TYPE_GYROSCOPE |
|
4 |
光线传感器 |
TYPE_LIGHT |
|
5 |
磁场传感器 |
TYPE_MAGNETIC_FIELD |
|
6 |
压力传感器 |
TYPE_PRESSURE |
|
7 |
临近传感器 |
TYPE_PROXIMITY |
|
8 |
湿度传感器 |
TYPE_RELATIVE_HUMIDITY |
|
9 |
方向传感器 |
TYPE_ORIENTATION |
|
10 |
重力传感器 |
TYPE_GRAVITY |
|
11 |
线性加速传感器 |
TYPE_LINEAR_ACCELERATION |
|
12 |
旋转向量传感器 |
TYPE_ROTATION_VECTOR |
注意:1-8是硬件传感器,9是软件传感器,其中方向传感器的数据来自重力和磁场传感器,10-12是硬件或软件传感器。
(3)SensorEvent类
系统使用该类创建传感器事件对象。该对象可以提供与传感器事件有关的信息。传感器事件对象包括的信息有原始的传感器回传数据、传感器类型、数据的精度以及触发事件的时间。
(4)SensorEventListener接口
该接口包含两个回调方法,当传感器的回传值或精度发生变化时,系统会调用这两个回调方法。
/** * 传感器精度变化时回调 */
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
/*** 传感器数据变化时回调*/
@Override
public void onSensorChanged(SensorEvent event) {
.
.
.
}
4.获取传感器技术参数
下来我们编写代码来获取一下自己手机的传感器技术参数。
TextView tvSensors = (TextView) findViewById(R.id.tv_sensors);
//获取传感器SensorManager对象
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
for (Sensor sensor : sensors) {
tvSensors.append(sensor.getName() + "\n");
}
(三)第三次实验
本次实验则是在上一次获取传感器数据的基础上实现屏幕绘图功能,和进一步完善数据的格式。
1.实时获取系统时间
1.获取系统时间的方法
2.通过DateFormat方式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");// HH:mm:ss
//获取当前时间
Date date = new Date(System.currentTimeMillis());
(2)第二种方法Calendar calendar = Calendar.getInstance();
(3)第三种方法Time t=new Time(); // or Time t=new Time("GMT+8"); 加上Time Zone资料
我选择通过SimpleDateFormat来获取系统的时间,但是存在一个问题,TextView的内容一旦设定好之后,就不会自动更新了,所以用一个线程,在线程里不断循环,线程每休眠1s,sendMessage给Handle,在handleMessage方法里更新UI线程即TextView的内容。
2.android如何在子线程中更新UI
android的UI更新不是线程安全的,换句话说就是不允许在子线程中进行UI的更新,如果想进行UI的更新,就必须在主线程中进行。
异步消息处理机制
Handler、Looper、Message都是与Android异步消息处理线程相关的概念。
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
1)Message
这个很容易理解,就是消息,他可以携带少量的消息,比如你在子线程中去更新UI,就将要更新的内容放到message中,然后将message发送到主线程中,由主线程去做更新UI的操作。也就是说,messge可以用于不同线程中进行数据交换4
2)Handler
这个就是处理者的意思。你可以在线程中调用Handler的sendMessage方法去发送消息,然后在主线程中,通过handleMessage去接受这个message消息,然后去处理。
3)MessageQueue
消息队列,Android主线程包含一个消息队列,它主要用于存放所有通过Handler发送的消息,可以是Message,也可以是Runnable。主线程在创建的时候会默认创建消息队列,子线程在创建的时候默认不会创建消息队列,但是可以手动创建。当在主线程创建一个Handler,这个Handler会自动绑定主线程以及主线程的消息队列。然后在子线程通过这个Handler发送消息的时候,这个消息就会被加入到主线程的消息队列了。
4)Looper
那么,通过Handler的sendMessage可以发送消息加入到主线程的消息队列中。那么,主线程在什么情况下,从消息队列中取出这些消息进行处理呢?就需要Looper了。Looper是每个线程中的消息队列的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,每当发现消息队列中有消息,就会取出来,然后传递到Handler的handleMessage()方法中。所以,我们只需要在主线程中完成handleMessage()方法的实现,剩下的就不需要你操心了,Looper会帮我们完成其他所有的事情的。
Looper和消息队列一样,主线程会自动创建,而且每个线程只有一个消息队列和一个Looper。
3.屏幕绘图功能
Android——Canvas类的使用
Canvas类(android.graphics.Canvas)
Canvas类就是表示一块画布,你可以在上面画你想画的东西。当然,你还可以设置画布的属性,如画布的颜色/尺寸等。Canvas提供了如下一些方法:
Canvas():创建一个空的画布,可以使用setBitmap()方法来设置绘制的具体画布;
Canvas(Bitmap bitmap):以bitmap对象创建一个画布,则将内容都绘制在bitmap上,bitmap不得为null;
Canvas(GL gl):在绘制3D效果时使用,与OpenGL有关;
drawColor:设置画布的背景色;
setBitmap:设置具体的画布;
clipRect:设置显示区域,即设置裁剪区;
isOpaque:检测是否支持透明;
rotate:旋转画布;
同时还要理解一个paint类,paint类拥有风格和颜色信息如何绘制几何学,文本和位图。
Paint 代表了Canvas上的画笔、画刷、颜料等等;
Paint类常用方法:
setARGB(int a, int r, int g, int b) // 设置 Paint对象颜色,参数一为alpha透明值
setAlpha(int a) // 设置alpha不透明度,范围为0~255
setAntiAlias(boolean aa) // 是否抗锯齿
setColor(int color) // 设置颜色,这里Android内部定义的有Color类包含了一些常见颜色定义
setTextScaleX(float scaleX) // 设置文本缩放倍数,1.0f为原始
setTextSize(float textSize) // 设置字体大小
setUnderlineText(booleanunderlineText) // 设置下划线
(四)第四次实验
1. 图表控件MPAndroidChart
一款在github上发布的,十分强大的图表框架。
2.添加第三方库
①直接搜索法
点击项目设置按钮
依次选择 App > Dependencies
依次选择 + > Library dependency
下图所表示的库和Gradle文件是对应的
点击GradleOK后,刷新,以及重新Build项目
②libs添加法
这里直接复制你的第三方jar包到目录app > lib下s ,如果没有libs就新建一个 首先点击Android ,切换到Project,重新Build项目
③Module添加法
新建了一个Module名称为 mylibrary,在App中调用这个类
然后我们开始添加Module,点击项目设置小按钮
app >Dependencies > + >3.Module dependency
选择Module,点击Build项目
④Gradle 直接添加法
找到build.gradle(Module:app),然后找到dependencies,
这里我们可以看到我们刚才添加的库,刚才那个项目设置是和这里是一一对应的。
我们这里以github上android 图表框架MPA为例,去到它的官网,我们可以看到它的请求网页:
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
⑤添加.so链接库法
如果你的app需要集成百度地图之类的,一定有这样的so动态库。
复制lib下的所有文件,到Android Studio的app > libs下
然后找到build.gradle(Module:app) 文件,在android节点下添加如下内容
sourceSets {
main {
jniLibs.srcDirs =[]
}
}
然后build一下
或者直接创建一个jniLibs目录,路径为:app > src > main >jniLibs
(五)第五次实验
1. Android的数据存储方式
Android提供了5种方式存储数据:
--使用SharedPreferences存储数据;
--文件存储数据;
--SQLite数据库存储数据;
--使用ContentProvider存储数据;
--网络存储数据;
先说下,Preference,File, DataBase这三种方式分别对应的目录是/data/data/Package Name/Shared_Pref, /data/data/Package Name/files,/data/data/Package Name/database 。 在Android中通常使用File存储方式是用Context.openFileOutput(StringfileName, int mode)和Context.openFileInput(String fileName)。 Context.openFileOutput(String fileName, int mode)生成的文件自动存储在/data/data/PackageName/files目录下,其全路径是/data/data/PackageName/files/fileName 。注意下,这里的参数fileName不可以包含路径分割符(如"/")。
通常来说,这种方式生成的文件只能在这个apk内访问。但这个结论是指使用Context.openFileInput(StringfileName)的方式。使用这种方式,每个apk只可以访问自己的/data/data/Package Name/files目录下的文件,原因很简单,参数fileName中不可以包含路径分割符,Android会自动在/data/data/Package Name/files目录下寻找文件名为fileName的文件。
(1)使用SharedPreferences存储数据
首先说明SharedPreferences存储方式,它是Android提供的用来存储一些简单配置信息的一种机制,例如:登录用户的用户名与密码。其采用了Map数据结构来存储数据,以键值的方式存储,可以简单的读取与写入,具体实例如下:
void ReadSharedPreferences(){
String strName,strPassword;
SharedPreferences user =
getSharedPreferences(“user_info”,0);
strName = user.getString(“NAME”,””);
strPassword = user getString(“PASSWORD”,””);
}
void WriteSharedPreferences(String strName,String strPassword){
SharedPreferences user =
getSharedPreferences(“user_info”,0);
uer.edit();
user.putString(“NAME”, strName);
user.putString(“PASSWORD” ,strPassword);
user.commit();
}
数据读取与写入的方法都非常简单,只是在写入的时候有些区别:先调用edit()使其处于编辑状态,然后才能修改数据,最后使用commit()提交修改的数据。实际上SharedPreferences是采用了XML格式将数据存储到设备中,在DDMS中的File Explorer中的/data/data/<package
name>/shares_prefs下。以上面的数据存储结果为例,打开后可以看到一个user_info.xml的文件,打开后可以看到:
<?xml version=”1.0″ encoding=”UTF-8″?>
<map>
<string name=”NAME”>moandroid</string>
<string name=” PASSWORD”>SharedPreferences</string>
</map>
使用SharedPreferences是有些限制的:只能在同一个包内使用,不能在不同的包之间使用。
(2)文件存储数据
文件存储方式是一种较常用的方法,在Android中读取/写入文件的方法,与Java中实现I/O的程序是完全一样的,提供了openFileInput()和openFileOutput()方法来读取设备上的文件。FilterInputStream, FilterOutputStream等可以到Java io package说明中去详细学习,不再此详细说明,具体实例如下:
String fn = “moandroid.log”;
FileInputStream fis = openFileInput(fn);
FileOutputStream fos = openFileOutput(fn,Context.MODE_PRIVATE);
除此之外,Android还提供了其他函数来操作文件,详细说明请阅读Android SDK。
(3)网络存储数据
网络存储方式,需要与Android 网络数据包打交道,关于Android 网络数据包的详细说明,请阅读Android SDK引用了Java SDK的哪些package。
(4)ContentProvider
ContentProvider简介
当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。
Uri类简介
Uri代表了要操作的数据,Uri主要包含了两部分信息:1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。
2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
² 要操作contact表中id为10的记录,可以构建这样的路径:/contact/10
- 要操作contact表中id为10的记录的name字段, contact/10/name
- 要操作contact表中的所有记录,可以构建这样的路径:/contact?
- 要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:
要操作xml文件中contact节点下的name节点,可以构建这样的路径:/contact/name
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri =Uri.parse("content://com.changcheng.provider.contactprovider/contact")
UriMatcher、ContentUrist和ContentResolver简介
因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。
UriMatcher:用于匹配Uri,它的用法如下:
1.首先把你需要匹配Uri路径全部给注册上,如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回匹配码为1
uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配
content://com.changcheng.sqlite.provider.contactprovider/contact/230路径,返回匹配码为2
uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2);//#号为通配符
2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回的匹配码为1。
ContentUris:用于获取Uri路径后面的ID部分,它有两个比较实用的方法:
withAppendedId(uri, id)用于为路径加上ID部分
parseId(uri)方法用于从路径中获取ID部分
ContentResolver:当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。
2. 文件存储
Android中内部存储,外部存储的概念
内部存储
概念:注意内部存储不是内存。内部存储位于系统中很特殊的一个位置,如果你想将文件存储于内部存储中,那么文件默认只能被你的应用访问到,且一个应用所创建的所有文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。当一个应用卸载之后,内部存储中的这些文件也被删除。从技术上来讲如果你在创建内部存储文件的时候将文件属性设置成可读,其他app能够访问自己应用的数据,前提是他知道你这个应用的包名,如果一个文件的属性是私有(private),那么即使知道包名其他应用也无法访问。 内部存储空间十分有限,因而显得可贵,另外,它也是系统本身和系统应用程序主要的数据存储所在地,一旦内部存储空间耗尽,手机也就无法使用了。所以对于内部存储空间,我们要尽量避免使用。Shared Preferences和SQLite数据库都是存储在内部存储空间上的。内部存储一般用Context来获取和操作。
访问内部存储的API方法:
1、Environment.getDataDirectory()
2、getFilesDir().getAbsolutePath()
3、getCacheDir().getAbsolutePath()
4、getDir(“myFile”,
MODE_PRIVATE).getAbsolutePath()
外部存储
概念:最容易混淆的是外部存储,因为老的Android系统的跟新的Android系统是有差别的,很多人去网上查找资料,看了一下以前的资料,又看了一下现在的资料,但是发现它们说法不一样然后就困惑了。首先说一个大家普遍的概念“如果在pc机上是区分外部存储和内部存储的话,那么电脑自带的硬盘算是内部存储,U盘或者移动硬盘就是外部存储了。”因此很多人带着这样的理解去看待安卓手机,把内置存储(机身存储)当做内部存储,而把扩展的SD卡当做是外部存储。这么认为确实没错,因为在4.4(API19)以前的手机上确实是这样的,手机自身带的存储卡就是内部存储,而扩展的SD卡就是外部存储。但是从4.4的系统开始,很多的中高端机器都将自己的机身存储扩展到了8G以上,比如有的人的手机是16G的,有的人的手机是32G的,但是这个16G,32G是内部存储吗,不是的!!!,它们依然是外部存储,也就是说4.4系统及以上的手机将机身存储存储(手机自身带的存储叫做机身存储)在概念上分成了”内部存储internal” 和”外部存储external” 两部分。
清除缓存和清除数据的区别:
清除缓存:我们知道应用程序在运行过程中需要经过很多过程,比如读入程序,计算,输入输出等等,这些过程中肯定会产生很多的数据,它们在内存中,以供程序运行时调用。所以清除缓存清除的是APP运行过程中所产生的临时数据。
清除数据:清除数据才是真正的删除了我们保存在文件中的数据(永久性数据,如果不人为删除的话会一直保存在文件中)例如当我们在设置里面清除了某个应用的数据,那么/data/user/0/packname//storage/emulated/0/Android/data/packname/下的文件里面的数据会全部删除,包括cache,files,lib,shared_prefs等等。
3. 对话框
对话框的几种实现方式:
- Activity
- Popwindow
- Dialog
- Fragment
关键类
- AlertDialog
- ProgressDialog
- DialogFragment
- Activity
特点介绍:
- Dialog 这类的实现google官方不推荐因为没有生命周期. 如果意外销毁会导致数据的丢失. 不过使用简单. 没什么特别的要求用这个
- DialogFragment 可以算的上是最佳实现. 如果对话框界面相对复杂的话可以用这个.
- Activity 自带渐变的过渡动画
- popwindow 可以附着到其他控件周围.
4.文件夹浏览
应用程序在运行的过程中如果需要向手机上保存数据,一般是把数据保存在SDcard中的。
大部分应用是直接在SDCard的根目录下创建一个文件夹,然后把数据保存在该文件夹中。这样当该应用被卸载后,这些数据还保留在SDCard中,留下了垃圾数据。
如果你想让你的应用被卸载后,与该应用相关的数据也清除掉,该怎么办呢?
通过Context.getExternalFilesDir()方法可以获取到SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据通过Context.getExternalCacheDir()方法可以获取到SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
如果使用上面的方法,当你的应用在被用户卸载后,SDCard/Android/data/你的应用的包名/ 这个目录下的所有文件都会被删除,不会留下垃圾信息。
通过Context.getFilesDir()方法可以获取到/data/data/youPackageName/files这个目录,一般放一些短时间保存的数据,同样引用卸载后,这个目录下的数据也会删除,不会留下垃圾信息。
而且上面二个目录分别对应 设置->应用->应用详情里面的”清除数据“与”清除缓存“选项
总结一下android 中的几种目录:
一、 files
1.Context.getFilesDir(),该方法返回/data/data/youPackageName/files的File对象。
2. Context.openFileInput()与Context.openFileOutput(),只能读取和写入files下的文件,返回的是FileInputStream和FileOutputStream对象。
3. Context.fileList(),返回files下所有的文件名,返回的是String[]对象。
4. Context.deleteFile(String),删除files下指定名称的文件。
5. Context.getExternalFilesDir()方法可以获取到
SDCard/Android/data/youPackageName/files/ 目录,一般放一些长时间保存的数据
二、cache
1.Context.getCacheDir(),该方法返回 /data/data/youPackageName/cache的File对象,这个文件里面的数据在设备内存不足的时候,会被系统删除数据。注意:你不能依赖系统帮你删除这些文件,当这个文件夹里面的数据超过1MB的时候,系统会删除这个文件夹里面的数据。
2. Context.getExternalCacheDir()方法可以获取SDCard/Android/data/youPackageName/cache/目录,一般存放临时缓存数据。
三、custom dir
getDir(Stringname, int mode),返回 /data/data/youPackageName/ 下的指定名称的文件夹File对象,如果该文件夹不存在则用指定名称创建一个新的文件夹。
创建文件的权限
MODE_PRIVATE:说明该文件只能被当前的应用程序所读写;
MODE_APPEND:以追加方式打开该文件,应用程序可以向该文件中追加内容;
MODE_WORLD_READABLE:该文件的内容可以被其他的应用程序所读取;
MODE_WORLD_WRITEABLE:该文件的内容可以被其他的应用程序所读、写;
5. 适配器的使用
概念
Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带。在常见的View(List View,Grid View)等地方都需要用到Adapter。如下图直观的表达了Data、Adapter、View三者的关系。
Android中所有的Adapter一览:
由图可以看到在Android中与Adapter有关的所有接口、类的完整层级图。在我们使用过程中可以根据自己的需求实现接口或者继承类进行一定的扩展。比较常用的有 Base Adapter,Impleader,Adapter,Counteradaptation等。
- BaseAdapter是一个抽象类,继承它需要实现较多的方法,所以也就具有较高的灵活性;
- ArrayAdapter支持泛型操作,最为简单,只能展示一行字。
- SimpleAdapter有最好的扩充性,可以自定义出各种效果。
- SimpleCursorAdapter可以适用于简单的纯文字型ListView,它需要Cursor的字段和UI的id对应起来。如需要实现更复杂的UI也可以重写其他方法。可以认为是SimpleAdapter对数据库的简单结合,可以方便地把数据库的内容以列表的形式展示出来。
列表的显示需要三个元素:
a.ListVeiw 用来展示列表的View。
b.适配器 用来把数据映射到ListView上的中介。
c.数据 具体的将被映射的字符串,图片,或者基本组件。
由于我们需要显示文件夹列表,所以需要用到适配器。
这次实验中列表不光会用来做显示用,我们要在上面添加按钮。添加按钮首先要写一个有按钮的xml文件,然后自然会想到定义一个适配器,然后将数据映射到布局文件上。但是事实并非这样,因为按钮是无法映射的,即使你成功的用布局文件显示出了按钮也无法添加按钮的响应,这时就要研究一下ListView是如何现实的了,而且必须要重写一个类继承BaseAdapter。
(六)第六次实验
android中service(服务)运行于后台,没有界面。和其他组件一样,service也运行在主线程中,因此不能用它来做耗时的请求或者动作。可以在服务中开启线程,在线程中做耗时操作。可以启动一个服务service来播放音乐,或者记录地理信息位置的改变,或者启动一个服务来运行并一直监听某种动作。
1.Service的种类
①本地服务,Local Service用于应用程序内部。
在Service可以调用Context.startService()启动,调用Context.stopService()结束。在内部可以调用Service.stopSelf() 或Service.stopSelfResult()来停止。无论调用了多少次startService(),都只需调用一次stopService()来停止。调用startForeground()方法可以将服务运行于前台并在通知栏展示。
②远程服务,Remote Service用于android系统内部的应用程序之间。
可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用Context.bindService()建立连接并启动,调用Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。提供给可被其他应用复用,比如定义一个天气预报服务,提供与其他应用调用即可。
2.Service的生命周期
①被启动的服务的生命周期:
如果一个Service被某个Activity调用Context.startService方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此停止服务只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。
②被绑定的服务的生命周期:
如果一个Service被某个Activity调用Context.bindService方法绑定启动,不管调用bindService调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService断开连接或者之前调用bindService的Context不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。
③被启动又被绑定的服务的生命周期:
如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStart便会调用多少次。调用unbindService将不会停止Service,而必须调用stopService或Service的stopSelf来停止服务。
④当服务被停止时清除服务:
当一个Service被终止(1、调用stopService;2、调用stopSelf;3、不再有绑定的连接(没有被启动))时,onDestroy方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。
四、实验步骤
(一)第一次实验
- 下载、安装JDK
- 设定JAVA_HOME以及CLASSPATH环境变量
- 下载、安装AndroidStudio
- Android Studio初始化
- SDK的下载和安装
- 设定ANDROID_SDK_HOME环境变量
- 新建hello world程序(按钮屏幕操作功能)
首先配置应用名称、工程名称以及包名。同时设置APP最小要求的SDK版本以及目标SDK版本以及编译版本以及应用的主题。
然后新建一个Activity活动,这里选择新建一个空的Activity,然后单击下一步即可。
至此我们就新建了一个HelloWorld工程。我们可以在AVD中运行此APP,得到如下图所示的效果:
至此,安卓开发环境以及Hello world!程序开发完成,接下来我们连接手机进行实验。
主程序如下:
int flag=0;
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b1;
b1=(Button)findViewById(R.id.button1);
final TextView t1;
t1=(TextView)findViewById(R.id.textView1);
b1.setOnClickListener(new
OnClickListener() {
@Override
public void onClick(View
arg0) {
flag++;
if(flag==1)
{t1.setText("OK");}
if(flag==2)
{t1.setText("bro");}
if(flag==3)
{
flag=0;
t1.setText("!!!!");
}
// TODO
Auto-generated method stub
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu
menu) {
// Inflate the menu; this adds items to
the action bar if it is present.
getMenuInflater().inflate(R.menu.main,
menu);
return true;
}
}
可以看到新建了一个继承自Activity的类,然后重载了这个类的onCreate方法,这个方法是在活动创建的时候被调用的,在这个方法中首先先调用父类的onCreate方法,然后再创建了一个Activity。onClick则是我们新建的方法,每当按钮被按下的时候这个方法就会被调用,在这个方法中,我们实现了文字变换的功能。我们设了一个新的变量flag用于对文字的不同显示,实现了按键的循环使用。在显示屏上的效果如图:
(二)第二次实验
加速的是描述物体运动速度变化快慢的物理量,以m/s2为单位。在静止时,加速度返回的值为地表静止物体的重力加速度,约为9.8m/s2。加速度传感器输出的信息存放在 SensorEvent的values数组中,此时的values数组会有三个值,分别代表手机在x轴,y轴,z轴方向上的加速度信息。
根据力学原理,我们知道重力的作用永远是向下的,所以当手机竖直时,重力作用在y轴,平放在z轴,横立则在x轴。根据输出的值和手机的空间坐标以及重力的作用原理就可以判断手机放置的状态,比如,当x轴的值接近重力加速度或者接近负的重力加速度,说明设备处于横立状态,同时值为正时左边朝下,值为负时右边朝下。
1、初始化
SensorManager manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
//指定监听的传感器类型
//all为全部,ACCELEROMETER为加速度,ORIENTATION为方向
Sensor orientsensor = manager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
2、注册监听
//方式1:当前activity实现SensorEventListener接口
manager.registerListener(Mainactivity.this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
//方式2:自定义传感器监听器,实现SensorEventListener
sensorManager.registerListener(MycustomSensorListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
3、实现方法OnSensorChange
获取 x,y,z三轴的坐标。
if (event.sensor.getType() == Sensor.TYPE_ORIENTATION){
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
}
4、注销
if (null != manager && null != sensor ){
manager.unregisterListener(MainActivity.this,sensor);
}
需要说明的是,不同手机的传感器灵敏度不同,而且传感器的调用是不间断的,所以使结果更准确,最好是取一段时间内的传感器变化,为适配大多数手机,需要设定一个阈值或者范围。
例如调用加速度传感器,检测手机摇一摇:
//检测的时间间隔
private static final int UPDATE_INTERVAL = 100;
private long mLastUpdateTime;
private float mLastX, mLastY, mLastZ;
//摇晃检测阈值,决定了对摇晃的敏感程度,越小越敏感
private int shakeThreshold = 1200;
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
long currentTime = System.currentTimeMillis();
long diffTime = currentTime - mLastUpdateTime;
if (diffTime < UPDATE_INTERVAL) {
return;
}
mLastUpdateTime = currentTime;
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float deltaX = x - mLastX;
float deltaY = y - mLastY;
float deltaZ = z - mLastZ;
mLastX = x;
mLastY = y;
mLastZ = z;
float delta = (float) (Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) / diffTime * 10000);
// 当检测到一个摇晃(加速度的差值大于指定阈值)
if (delta > shakeThreshold ) {
dosomething();
}
}
}
关于方向传感器坐标轴:
z是指向地心的方位角,x轴是仰俯角(由静止状态开始前后反转),y轴是翻转角(由静止状态开始左右反转)。
方向传感器结合地磁传感器可以实现指南针功能。单独使用,可以检测手机的方向,例如竖屏横屏状态。
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ORIENTATION){
long currentTime = System.currentTimeMillis();
long diffTime = currentTime - mLastUpdateTime;
if (diffTime < UPDATE_INTERVAL) {
return;
}
mLastUpdateTime = currentTime;
float x = event.values[0];
if ( 260 < x && x < 290 ){
screenStatus = 1;//右横屏
}else if (80 < x && x < 110){
screenStatus = 2;//左横屏
}else {
screenStatus = 0;//竖屏
}
}
}
(三)第三次实验
1.系统时间实时获取
通过SimpleDateFormat来获取系统的时间,但是存在一个问题,TextView的内容一旦设定好之后,就不会自动更新了,所以用一个线程,在线程里不断循环,线程每休眠1s,sendMessage给Handle,在handleMessage方法里更新UI线程即TextView的内容。
public class TestClass extends Activity {
private TextView tv_time;
private static final int msgKey1 = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
tv_time = (TextView) findViewById(R.id.mytime);
new TimeThread().start();//在onCreate()中,启动线程
}
//创建一个子线程,并在子线程中发送消息
public class TimeThread extends Thread{
@Override
public void run() {
super.run();
do{
try {
Thread.sleep(1000);
Message msg = new Message();
msg.what = msgKey1;
mHandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}while (true);
}
}
//创建一个Handler实例,并实现handleMessage方法
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case msgKey1:
long time = System.currentTimeMillis();
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 EEE");
tv_time.setText(format.format(date));
break;
default:
break;
}
}
}
}
2.屏幕绘图功能
结合实时采集的传感器数据,利用canvas在画布上作图。作图过程中需要注意横坐标的变换。
if(flag==true){
//锁定整个SurfaceView
Canvas mCanvas = mSurfaceHolder.lockCanvas();
try {
if (mCanvas != null) {
//画笔的颜色(红)
mPaint.setColor(Color.RED);
//画X轴的点
mCanvas.drawPoint(m, (int) (100 + 2* event.values[0]), mPaint);
//画笔的颜色(绿)
mPaint.setColor(Color.GREEN);
//画Y轴的点
mCanvas.drawPoint(m, (int) (200 + 2* event.values[1]), mPaint);
//画笔的颜色(蓝)
mPaint.setColor(Color.BLUE);
//画Z轴的点
mCanvas.drawPoint(m, (int) (300 + 2* event.values[2]), mPaint);
//横坐标+1
m++; //如果已经画到了屏幕的最右边
if(m > getWindowManager().getDefaultDisplay().getWidth())
{ m = 0; //清屏 mCanvas.drawColor(Color.BLACK); }
//绘制完成,提交修改
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mCanvas != null) {
//重新锁一次 mSurfaceHolder.lockCanvas(new Rect(0, 0, 0, 0)); mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
//定义一个类,实现Callback接口
public class MyHolder implements SurfaceHolder.Callback {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
//add your code
}
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
//add your code }
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
//add your code
}
}
绘图截图:
实验结果截图:
由于手机没有插卡和联网,所以时间为系统时间,与实际时间有较大出入
- 打开程序
- 点击start按钮,开始作图
- 点击off按钮停止作图
(四)第四次实验
1. 日期获取
Calendar calendar = Calendar.getInstance();
//获取系统的日期
//年
int year = calendar.get(Calendar.YEAR);
//月
int month = calendar.get(Calendar.MONTH)+1;
//日
int day = calendar.get(Calendar.DAY_OF_MONTH);
2.字符拼接
我们所知道的字符串String的拼接有: “+” 、 concat () 方式实现,或者使用StringBuilder、StringBuffer类实现。
这几种方式性能的从低到高进行排序,则顺序为:“+” < concat () < StringBuffer < StringBuilder 。StringBuilder的性能是最高的,StringBuilder与StringBuffer两者的区别是:StringBuffer是线程安全的,而StringBuilder不是。在高并发的应用中,应该考虑使用StringBuffer。
在加速度传感器函数内部的数据处理采用StringBuilder,而在函数外部的数据采用StringBuffer :
①StringBuilder 进行字符拼接并用substring数据截断
StringBuilder s2=new StringBuilder();
String s3_sub = null;
float[] values=event.values;
sb.append("X方向上的加速度");
sb_sub = sb.toString();
s2_sub = s2_sub.substring(0,13);
②StringBuffer 进行字符拼接并用valueOf数据类型转换
int day = calendar.get(Calendar.DAY_OF_MONTH);
String date3=String.valueOf(day);
StringBuffer buf=new StringBuffer();
buf.append(date1);
3. 第三方库
project build.gradle 中allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}app build.gradle 中compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
4.绘制表格
.X轴为当前时间,只需要Y轴数据即可2.X轴的值为字符串,而Entry的构造方法参数全为float public Entry(float x, float y) 所以需要另外定义X轴的值简要代码 动态添加值EntryEntry entry = new Entry(lineDataSet.getEntryCount(), number);
lineData.addEntry(entry, 0);
//通知数据已经改变
lineData.notifyDataChanged();
lineChart.notifyDataSetChanged();
//设置在曲线图中显示的最大数量
lineChart.setVisibleXRangeMaximum(10);
//移到某个位置
lineChart.moveViewToX(lineData.getEntryCount() - 5);X轴值的设定我并没有找到直接设置X轴为当前时间的方法,而是新建一个字符串集合,选择在每次添加Entry的时候将当前时间添加进集合,然后设置X轴的值private SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//设置日期格式
private List<String> timeList = new ArrayList<>(); //存储x轴的时间xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return timeList.get((int) value % timeList.size());
}
});
(五)第五次实验
1. 自定义一个文件写入操作类Filestorage.java
实现的功能大致有:
- 文件的新建、删除;
- 文件的复制;获取文件扩展名;
- 文件的重命名;
- 文件的写入;
- 用时间戳来实现文件的命名,保证文件不会重名
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static com.github.mikephil.charting.charts.Chart.LOG_TAG;
/**
* Created by laili on 2017/4/18.
* 保存数据到文件
*/
public class FileStorage {
/**
* 文件对象
*/
private File file;
public FileStorage(String
filePath){
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
File dir
=getAlbumStorageDir(filePath);
file = new File(dir,sdf.format(d)+".txt");
}
/**
* 获取文件夹,如果不存在就创建
* @param dirName 文件夹名称
* @return File file 文件夹File对象
*/
private File getAlbumStorageDir(String dirName) {
// Get the directory for the user's public pictures directory.
File file = newFile(Environment.getExternalStorageDirectory(),
dirName);
if (!file.exists()&&!file.mkdirs())
{
Log.e(LOG_TAG, "Directory
not created");
}
return file;
}
/**
* 对文件添加数据
* @param data 数据
*/
public void addData(List data){
try{
String string="";
for (int i = 0; i <data.size() ; i++) {
String tmp=(String) data.get(i);
string+=tmp+"\n";
}
FileOutputStream outputStream
= new FileOutputStream(file);
outputStream.write(string.getBytes());
outputStream.close();
}catch(Exception
e){
e.printStackTrace();
}
}
public List
getData(){
return new ArrayList();
}
/**
* 获取文件对象
* @return file 文件对象
*/
public File getFile(){
return file;
}
}
2. 自定义一个文件夹类
为了让主Activity更加简洁,将获取文件夹路径的功能在一个类中实现,在主Activity中只需要调用即可。
FileBrowserActivity.java
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import java.io.File;
public class FileBrowserActivity extends Activity implements
View.OnClickListener, MyAdapter.FileSelectListener {
private TextViewcurPathTextView;
private StringrootPath = "";
private MyAdapterlistAdapter;
//初始化进入的目录,默认目录
private String filePath= "";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file_browser_acitivity);
initView();
//跟目录
rootPath = getIntent().getStringExtra("rootPath");
//指定文件夹
filePath = getIntent().getStringExtra("path");
curPathTextView.setText(filePath);
filePath = filePath.isEmpty() ? rootPath: filePath;
View layoutFileSelectList = findViewById(R.id.layoutFileSelectList);
listAdapter = new MyAdapter(layoutFileSelectList, rootPath, filePath);
listAdapter.setOnFileSelectListener(this);
findViewById(R.id.btnSure).setOnClickListener(this);
findViewById(R.id.btnCancel).setOnClickListener(this);
}
@Override
public void finish()
{
Intent intent = new Intent();
intent.putExtra("file", filePath);
setResult(RESULT_OK, intent);
super.finish();
}
private void initView()
{
curPathTextView = (TextView) findViewById(R.id.curPath);
}
@Override
public void onFileSelect(FileselectedFile) {
filePath =selectedFile.getPath();
}
@Override
public void onDirSelect(FileselectedDir) {
filePath =selectedDir.getPath();
}
@Override
public void onClick(Viewv) {
switch (v.getId()){
case R.id.btnSure:
finish();
break;
case R.id.btnCancel:
filePath ="";
finish();
break;
default:
break;
}
}
}
3. 自定义的适配器类:MyAdapter.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class MyAdapter extends BaseAdapterimplements View.OnClickListener, AdapterView.OnItemClickListener
{
private StringrootPath;
private LayoutInflatermInflater;
private BitmapmIcon3;
private BitmapmIcon4;
private List<File>fileList;
private Viewheader;
private ViewlayoutReturnRoot;
private ViewlayoutReturnPre;
private TextViewcurPathTextView;
private Stringsuffix = "";
private StringcurrentDirPath;
private FileSelectListenerlistener;
public MyAdapter(View
fileSelectListView, String rootPath, String defaultPath) {
this.rootPath = rootPath;
Context context =fileSelectListView.getContext();
mInflater =LayoutInflater.from(context);
mIcon3 =
BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_fodler);
mIcon4 = BitmapFactory.decodeResource(context.getResources(),R.drawable.icon_file);
curPathTextView = (TextView) fileSelectListView.findViewById(R.id.curPath);
header =fileSelectListView.findViewById(R.id.layoutFileListHeader);
layoutReturnRoot = fileSelectListView.findViewById(R.id.layoutReturnRoot);
layoutReturnPre = fileSelectListView.findViewById(R.id.layoutReturnPre);
layoutReturnRoot.setOnClickListener(this);
layoutReturnPre.setOnClickListener(this);
if (defaultPath!= null && !defaultPath.isEmpty()) {
getFileDir(defaultPath);
} else {
getFileDir(rootPath);
}
ListView listView = (ListView)fileSelectListView.findViewById(R.id.list);
listView.setAdapter(this);
listView.setOnItemClickListener(this);
}
private class ViewHolder
{
TextView text;
ImageView icon;
}
public interface FileSelectListener {
void onFileSelect(FileselectedFile);
void onDirSelect(FileselectedDir);
}
public void setOnFileSelectListener(FileSelectListenerlistener) {
this.listener = listener;
}
/**
* 获取所选文件路径下的所有文件,并且更新到listview中
*/
private void getFileDir(String filePath) {
File file = new File(filePath);
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isFile()&&!suffix.isEmpty()){
return pathname.getName().endsWith(suffix);
}
return true;
}
});
fileList =Arrays.asList(files);
//按名称排序
Collections.sort(fileList, newComparator<File>() {
@Override
public int compare(Fileo1, File o2) {
if (o1.isFile() && o2.isDirectory())
return 1;
if (o1.isDirectory() && o2.isFile())
return -1;
return o1.getName().compareTo(o2.getName());
}
});
if (header != null) {
header.setVisibility(filePath.equals(rootPath) ? View.GONE: View.VISIBLE);
}
notifyDataSetChanged();
if (curPathTextView != null)
{
curPathTextView.setText(filePath);
}
currentDirPath = filePath;
if (listener!=null){
listener.onDirSelect(file);
}
}
public int getCount()
{
return fileList.size();
}
public ObjectgetItem(int position) {
return fileList.get(position);
}
public long getItemId(int position) {
return position;
}
public View
getView(int position, View convertView, ViewGroupparent) {
ViewHolder holder;
if (convertView== null) {
convertView = mInflater.inflate(R.layout.file_item, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder)
convertView.getTag();
}
File file = fileList.get(position); holder.text.setText(file.getName());
if (file.isDirectory())
{
holder.icon.setImageBitmap(mIcon3);
} else {
holder.icon.setImageBitmap(mIcon4);
}
return convertView;
}
@Override
public void onItemClick(AdapterView<?>parent, View view, intposition, long id) {
File file = fileList.get(position);
if (file.isDirectory())
{
getFileDir(file.getPath());
} else {
if (listener!=null){
listener.onFileSelect(file);
}
}
}
@Override
public void onClick(View
v) {
if (v.getId()== R.id.layoutReturnRoot) {
getFileDir(rootPath);
} else if (v.getId()== R.id.layoutReturnPre) {
getFileDir(new File(currentDirPath).getParent());
}
}
(六)第六次实验
1.对之前文件存储路径选择的修复
X轴为当前时间,只需要Y轴数据即可2.X轴的值为字符串,而Entry的构造方法参数全为float public Entry(float x, float y) 所以需要另外定义X轴的值简要代码 动态添加值EntryEntry entry = new Entry(lineDataSet.getEntryCount(), number);
lineData.addEntry(entry, 0);
//通知数据已经改变
lineData.notifyDataChanged();
lineChart.notifyDataSetChanged();
//设置在曲线图中显示的最大数量
lineChart.setVisibleXRangeMaximum(10);
//移到某个位置
lineChart.moveViewToX(lineData.getEntryCount() - 5);X轴值的设定我并没有找到直接设置X轴为当前时间的方法,而是新建一个字符串集合,选择在每次添加Entry的时候将当前时间添加进集合,然后设置X轴的值private SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//设置日期格式
private List<String> timeList = new ArrayList<>(); //存储x轴的时间xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return timeList.get((int) value % timeList.size());
}
});
2.Service添加
①注册
<service
android:name=".MyService"
android:enabled="true"
android:exported="true" />
②方法重载
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
System.out.println("创建服务");
}
@Override
public int onStartCommand(Intent intent,int flags, int startId) {
System.out.println("启动服务..."); //这里实现服务的核心业务
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
System.out.println("停止服务");
super.onDestroy();
}
③调用
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent); //启动服务
// Intent stopIntent = new Intent(this,MyService.class);
//stopService(stopIntent); //停止服务
3.个性化图标
android:icon="@drawable/ic_launcher"
五、实验结果截图
- 第二次实验
按一次按键Test后,显示OK
(二) 第二次实验
1.手机侧放
2.手机竖直
3.手机平放
(三)第三次实验
由于手机没有插卡和联网,所以时间为系统时间,与实际时间有较大出入
1.打开程序
2.点击start按钮,开始作图
3.点击off按钮停止作图
(四)第四次实验
1.手机侧放
2.手机竖直
3.手机平放
- 移动手机
(五)第五次实验
实现绘图功能及数据存储到特定位置
完善功能,加入文件路径浏览功能
打开目录
文件内容为
用手机上的电子书进行查看(此时保留了小数点后两位,后面我们对代码进行了修改,可以显示小数点后三位)
手机上将会返回选择的文件存储路径(请自行忽略掉手机的碎屏)
(六)第六次实验
- 选择路径
- 查看读取
3.在息屏后点亮,查看文件
可以看到,在息屏的时间点,仍然有数据存入。
六、遇到的问题及解决方法
1.Error Installing APK
Android Studio 提示Session 'app':Error Installing APK
在使用Android Studio的时候,使用真机调试,运行不成功,提示下面图片中的错误(Session 'app':Error Installing APK)。
一般来讲,解决这个问题的方法有以下几种:
1.重新编译
2.重新插拔数据线
3.更改Studio的设置,将下方的第一个复选框的勾去掉
2.子线程无法更新UI:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
一个Android 程序默认情况下也只有一个进程,但一个进程下却可以有许多个线程。
在这么多线程当中,把主要是负责控制UI界面的显示、更新和控件交互的线程称为UI线程,由于onCreate()方法是由UI线程执行的,所以也可以把UI线程理解为主线程。其余的线程可以理解为工作者线程。
为了实现动态的图表显示,选用了两种Android更新UI的两种方法:handler与runOnUiThread()。
利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新 ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。 这样Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程。
实例化一个Handler对象,而在线程中通过sendMessage发送界面更新消息,并重写handleMessage方法调用thread.start()实现UIThread线程调用。
3.onSensorChanged无法取得数据
我们由onSensorChanged()取得加速度的x,y,z的值,但是一个无返回值的函数,无法直接取得数据进行图表绘制。以下有几种传数据的方式,考虑之后都没有采用,经过破坏数据的封闭性,进行由全局变量的传输,可以较简单得到数据。
许多编程语言都有2种方法将参数传递给方法------按值传递和按引用传递。
与其他语言不同,Java不允许程序员选择按值传递还是按引用传递各个参数,基本类型(byte--short--int--long--float--double--boolean--char)的变量总是按值传递。就对象而言,不是将对象本身传递给方法,而是将对象的的引用或者说对象的首地址传递给方法,引用本身是按值传递的-----------也就是说,讲引用的副本传递给方法(副本就是说明对象此时有两个引用了),通过对象的引用,方法可以直接操作该对象(当操作该对象时才能改变该对象,而操作引用时源对象是没有改变的)。
说到数组:如果将单个基本类型数组的元素传递给方法,并在方法中对其进行修改,则在被调用方法结束执行时,该元素中存储的并不是修改后的值,因为这种元素是按值传递,如果传递的是数组的引用,则对数组元素的后续修改可以在原始数组中反映出来(因为数组本身就是个对象,int[] a = new int[2];,这里面的int是数组元素的类型,而数组元素的修改是操作对象)。
对于单个非基本类型数组的元素在方法中修改,则在被调用方法结束执行时,该元素中存储的是修改后的值,因为这种元素是按引用传递的,对象的改动将在源数组的数组元素中反映出来。
4.文件选择路径存储
首先在任意路径存储数据,若用户需要指定路径时将其复制过来。
public void copyFile(String oldPath, String newPath) {
try {
int bytesum = 0;
int byteread = 0;
File oldfile = new File(oldPath);
if (oldfile.exists()) { //文件存在时
InputStream inStream = new FileInputStream(oldPath); //读入原文件
FileOutputStream fs = new FileOutputStream(newPath);
byte[] buffer = new byte[1444];
int length;
while ( (byteread = inStream.read(buffer)) != -1) {
bytesum += byteread; //字节数 文件大小
System.out.println(bytesum);
fs.write(buffer, 0, byteread); }
inStream.close(); } }
catch (Exception e) {
System.out.println("复制单个文件操作出错");
e.printStackTrace(); }
}
5.APK无法生成
Information:Gradle tasks [:assembleDebug, :filebrowser:assembleDebug, :mydialog:assembleDebug]
从提上看,是一个non-jumbo,让我想到了Dex的jumbo模式,这是一个用来配置制定该Dex是不是一个巨大的Dex的。报错的日志里显示是一个模块,从这可以推断出基本的问题:该模块需要生成一个Dex放进AAR包里给App使用,现在这个Dex生成不了,提示太了。
6.设置小数点后三位
DecimalFormat format 方法
eg:
new DecimalFormat("00.000").format(pi) //结果:03.142
7.设置每秒采集100个数据
Android手机中的传感器在注册监听的时候,需要设置一个频率,其实这个频率可以理解为获取传感器状态和值的频率,在Android编程中 SensorManager的频率总共分为4等,SENSOR_DELAY_FASTEST是最灵敏的,经过测试,可以看到,符合实验要求。
感应检测Sensor的延迟时间:
|
参数 |
延迟时间 |
|
SensorManager.SENSOR_DELAY_FASTEST |
0ms |
|
SensorManager.SENSOR_DELAY_GAME |
20ms |
|
SensorManager.SENSOR_DELAY_UI |
60ms |
|
SensorManager.SENSOR_DELAY_NORMAL |
200ms |
void register() {
mSensorManager.registerListener(mSensorEventListener,mSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
8.解决数据文件被覆盖的问题
以当前数据的采集时间命名测得的加速度传感器数据.txt文件:
//获取当前时间
java.util.Date d = new java.util.Date();
//设置其格式,"yyyy-MM-dd HH:mm:ss",作为txt文档的名字
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
/** * 获取文件夹,如果不存在就创建 */
File file = new file(Environment.getExternalStorageDirectory(), dirName);
//用当前数据的采集时间为名,实例化file对象
File file = new File(fileStorage.getFile().getPath(),hpath+"/"+sdf.format(d)+".txt");
七、实验总结与体会
1.Android传感器编程经验总结
Android手机操作系统是一款开源的操作系统,可以让用户灵活的进行编程来满足自己的各种需求。结合手机Android传感器的相关应用研究了下,现做一个小结:
1. Accelrator的x,y,z轴的正负向:
手机屏幕向上水平放置时: (x,y,z) = (0, 0, -10) 而不是 (0, 0, 10)
当手机顶部抬起时: y减小,且为负值
当手机底部抬起时: y增加,且为正值
当手机右侧抬起时: x减小,且为负值
当手机左侧抬起时: x增加,且为正值
2. Accelrator的z轴的变化:
手机屏幕向上水平放置时,z= -10
手机屏幕竖直放置时, z= 0
手机屏幕向下水平放置时,z= 10
3. 当x变为+5时, 手机画面切换为竖向
当x变为-5时, 手机画面切换为横向
4. Android传感器相关的类在SDK1.1和SDK1.5中不一样,因此实现代码也不一样
5. Android传感器类型分为:方向、加速表、光线、磁场、临近性、温度等
程序中分别为:
方向: SensorManager.SENSOR_ORIENTATION,
加速表: SensorManager.SENSOR_ACCELEROMETER
光线: SensorManager.SENSOR_LIGHT
磁场: SensorManager.SENSOR_MAGNETIC_FIELD
临近性: SensorManager.SENSOR_PROXIMITY
温度: SensorManager.SENSOR_TEMPERATURE
采样率:最快、游戏、普通、用户界面。当应用程序请求特定的采样率时,其实只是对Android传感器子系统的一个提示,或者一个建议。不保证特定的采样率可用。
最快: SensorManager.SENSOR_DELAY_FASTEST
游戏: SensorManager.SENSOR_DELAY_GAME
普通: SensorManager.SENSOR_DELAY_NORMAL
用户界面: SensorManager.SENSOR_DELAY_UI
准确性: 高、低、中、不可靠。
6. Orientation Sensor三个坐标的含义:
values[0]:方位角(水平旋转角),简单的说就是手机的头现在朝向哪个方位,0=北、90=东、180=南、270=西(可是好像不太准)
values[1]:纵向旋转角,0=面朝上平置、-90=垂直向上、-180/180=面朝下平置、90=垂直向下
values[2]:橫向旋转角,0=朝前、90=往右倒、-90=往左倒
2.屏幕绘图编程经验总结
界面是软件与用户交互的最直接的层,界面的好坏决定用户对软件的第一印象。Android系统能够在诸多的移动平台中脱颖而出,漂亮的界面带来的良好的用户体验无疑是其中一个很重要的因素。在我们平时的软件开发中,仅靠系统提供的那些组件来实现界面是远远不够的,在很多情况下我们都需要自己来绘制软件界面。
Android SDK提供了对基本图形以及位图的绘制,所有的绘图操作通常都是在View类的onDraw()方法中进行的。
在Android中绘图只需要继承View类,并重写它的onDraw()方法就可以了。在具体的绘图过程中可能会涉及Paint类、Color类、Canvas类等。其中,Paint类表示画笔,通过它可以设置画笔的精细、样式等,只有先得到画笔才能进行图形的绘制;Color类主要定义了一些颜色常量,利用它可以画出各种彩色的图形; Canvas类相当于画布,除了可以在它上绘制之外,还可以设置它的属性,比如,画布的颜色、尺寸等。
一般情况下,应用程序的组件都是在相同的GUI线程中绘制的,这个主应用程序线程同时也用来处理所有的用户交互(例如,按钮单击或者文本输入)操作。在Android中,任何一个View的子类只需要重写onDraw()方法,就可以实现界面的定制显示。对于一个应用来说除了图形的显示之外还需要有交互功能,比如图形的移动、变形等,但由于Android UI不是线程安全的,而界面刷新操作又必须得在UI线程中执行。为了解决这个问题,我们一般是利用Handler来实现UI线程的更新(通过调用View对象的invalidate()方法)。
Android中的View类提供了onKeyDown、onKeyUP、onTouchEvent、onTrackballEvent等方法来处理用户界面和用户交互所发生的事件。故我们的View类只要重写了这些的方法,当有按键按下或弹起等事件发生时,与之对应的事件处理方法就会被调用。
在这次实验中我们使用了GitHub上的开源框架MPAndroidChart:v3.0.2来绘制图像,对它的函数以及各种参数进行了充分的学习。GitHub是个优秀的学习网站,我们可以在上面看到很多大牛写的框架和示例程序,认真地学习这些优秀的程序,我们可以得到快速的提升。同时我们可以将自己的代码放在GitHub上进行管理,方便操作和今后的继续学习。
3.实验心得体会
在本学期的实验课程中我们进行了Android简单的手机APP开发编写。这次的实验在老师和助教学姐的指导以及我们自行查阅资料实现上所要求的功能。通过此次实验,我们感受到了JAVA编程的乐趣,对于进一步的学习也充满了热情。本次实验给我最大地收获就是接触到了一个跟自己地生活息息相关又与自己地专业紧密联系地知识,这也极大地激发了我对于Android开发地兴趣。
附录一、使用说明
1.APP功能描述
I)调用传感器,采集数据;
2)使用屏幕显示功能,实时显示采集的数据;
3)使用屏幕绘图功能,实时绘制采集的传感器数据;
4)使用对话框方式设置存储位置,保存数据
2.界面简介
①主界面
②文件选择界面
3.按钮功能简介
①选择文件默认存储路径
功能:
给予测得的加速度传感器数据一个默认的存储路径,在根目录下创建创建hpath文件夹,并在文件夹内创建以当前数据的采集时间命名的.txt文件
②选择文件存储路径
功能:
自行选择一个存储文件的路径,在所选择的路径下创建以当前数据的采集时间命名的.txt文件。
③数据保存开关
功能:打开开关实现数据的存储,关闭开关则不存储。
④开始测试开关
功能:打开开关实现传感器数据的获取,并根据获取的数据绘图;关闭开关则不进行以上操作。
⑤重新绘图按钮
功能:点击按钮实现重新绘图,表现为清屏后重新绘制
⑥文件存储路径返回
功能:返回用户自行选择的文件存储路径
4.使用流程简介
附录二、代码
1.MainActivity.java
package com.filebrowser;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Message;
//import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.RatingBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Toast;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import com.filebrowser.*;
import com.filebrowser.Storage.FileStorage;
//app.xyz.lailin.Rsensor.Storage.FileStorage;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.github.mikephil.charting.charts.LineChart;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import static com.github.mikephil.charting.charts.Chart.LOG_TAG;
public class MainActivity extends Activity {
public boolean isSaveData=false;
public static final int FILE_RESULT_CODE = 1;
private Button btn_open;
private TextView changePath;
private String rootPath;
private String hpath;
public String filePath;
TextView time1;
static final int msgKey2 = 1;
private TextView textValX;
private TextView textValY;
private TextView textValZ;
private ChartView chart;
LineChart mLineChart;
private List<String> accData=new ArrayList<String>();
private boolean flag = false ;
boolean tag = false;
FileStorage fileStorage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
fileStorage = new FileStorage("a");
String TAG = "onclick";
Log.i(TAG,"启动服务");
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent); //启动服务
// Intent stopIntent = new Intent(this,MyService.class);
//stopService(stopIntent); //停止服务
//break;
time1 = (TextView) findViewById(R.id.tv_time1);
textValX=(TextView)findViewById(R.id.valueX);
textValY=(TextView)findViewById(R.id.valueY);
textValZ=(TextView)findViewById(R.id.valueZ);
new TimeThread().start();
mLineChart=(LineChart)findViewById(R.id.chart);
chart=new ChartView(mLineChart);
//加速度传感器初始化
final Acceleration acceleration=new Acceleration();
//文件保存
//用filestorage类创了个新的文件夹
//获取switch btn用于开关
final Switch switchButton=(Switch)findViewById(R.id.switchButton);
//通过switch btn的状态设置是否监听
switchButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked){
acceleration.register();
toast("测试开始!");
}else {
acceleration.unRegister();
if (isSaveData){
fileStorage.addData(accData);
java.util.Date d = new java.util.Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);//当前时间获取,作为txt文档的名字
getAlbumStorageDir(filePath);
copyFile(fileStorage.getFile().getPath(),hpath+"/"+sdf.format(d)+".txt");
toast("测试结束!文件已保存在:"+hpath+"/"+sdf.format(d)+".txt");
}else {
toast("测试结束!");
}
}
switchButton.setChecked(isChecked);
}
});
final Switch isSaveSwitch=(Switch) findViewById(R.id.isSave);
isSaveSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (flag) {
isSaveData = isChecked;
}
}
});
}
private void initView() {
btn_open = (Button) findViewById(R.id.btn_open);
changePath = (TextView) findViewById(R.id.changePath);
}
private void initListener() {
btn_open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openBrowser();
}
});
findViewById(R.id.btn_open1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openBrowser1();
}
});
}
private void openBrowser() {
//rootPath = System.getenv("SECONDARY_STORAGE");
rootPath = System.getenv("hpath");
if (rootPath == null) {
rootPath = Environment.getExternalStorageDirectory().toString();
}
if ((rootPath.equals(Environment.getExternalStorageDirectory().toString()))) {
filePath = rootPath + "/hpath";
Intent intent = new Intent(MainActivity.this, FileBrowserActivity.class);
//根目录
intent.putExtra("rootPath", rootPath);
//进去指定文件夹
intent.putExtra("path", filePath);
startActivityForResult(intent, FILE_RESULT_CODE);
}
}
private void openBrowser1() {
rootPath = getSdcardPath();
if (rootPath == null || rootPath.isEmpty()) {
rootPath = Environment.getExternalStorageDirectory().toString();
}
//filePath = rootPath + "/hpath";
Intent intent = new Intent(MainActivity.this, FileBrowserActivity.class);
intent.putExtra("rootPath", rootPath);
intent.putExtra("path", filePath);
startActivityForResult(intent, FILE_RESULT_CODE);
}
public String getSdcardPath() {
String sdcardPath = "";
String[] pathArr = null;
StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE);
try {
Method getVolumePaths = storageManager.getClass().getMethod("getVolumePaths");
pathArr = (String[]) getVolumePaths.invoke(storageManager);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (pathArr != null && pathArr.length >= 3) {
sdcardPath = pathArr[1];
}
return sdcardPath;
}
@Override
//protected
//public void onActivityResult(int requestCode, int resultCode, Intent data) {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (FILE_RESULT_CODE == requestCode) {
Bundle bundle = null;
if (data != null && (bundle = data.getExtras()) != null) {
String path = bundle.getString("file","");
if(!path.isEmpty()){
changePath.setText("选择路径为 : " + path);
flag=true;
hpath =path;
}
}
}
}
//之后是画图的功能
public void restart(View v){
accData=new ArrayList<>();
chart.reset();
chart=null;
chart=new ChartView(mLineChart);
}
private void lineControl(Switch mSwitch, final int id){
mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
chart.getDataSets().get(id).setVisible(isChecked);
}
});
}
private void toast(String string){
Toast toast = Toast.makeText(MainActivity.this, string, Toast.LENGTH_SHORT);
toast.show();
}
/**
* 加速度传感器的相关设置,以及监听
*/
public class Acceleration {
/**
* 上一次的值
*/
private float[] prevData = new float[3];
private float comfort = 0.0f;
/**
* 传感器控制
*/
private SensorManager mSensorManager = null;
/**
* 传感器
*/
private Sensor mSensor = null;
/**
* 构造函数,传感器初始化
*/
Acceleration() {
//获取系统服务(SENSOR_SERVICE)返回一个SensorManager对象
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
//通过SensorManager获取相应的(加速度感应器)Sensor类型对象
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
/**
* 注册加速度传感器
*/
void register() {
mSensorManager.registerListener(mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_FASTEST
);
}
/**
* 取消注册加速度传感器
*/
void unRegister() {
mSensorManager.unregisterListener(mSensorEventListener, mSensor);
}
/**
* 加速度传感器监听方法
*/
private final SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
if (prevData != null) {
comfort = 0f;
for (int i = 0; i < 3; i++) {
if (Math.abs(prevData[i] - event.values[i]) > comfort) {
comfort = Math.abs(prevData[i] - event.values[i]);
}
}
//setComfortRate(rate);
}
System.arraycopy(event.values, 0, prevData, 0, 3);
/*显示左右、前后、垂直方向加速度*/
DecimalFormat decimalFormat = new DecimalFormat("00.000");
long time = System.currentTimeMillis();
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd, HH:mm:ss ");
//写入数据到txt文档中
accData.add(format.format(date)+" ,"+decimalFormat.format(x) + "," + decimalFormat.format(y) + "," + decimalFormat.format(z));
textValX.setText("X:"+decimalFormat.format(x));
textValY.setText("Y:"+decimalFormat.format(y));
textValZ.setText("Z:"+decimalFormat.format(z));
chart.addData(x, 0);
chart.addData(y, 1);
chart.addData(z, 2);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
}
private File getAlbumStorageDir(String dirName) {
// Get the directory for the user's public pictures directory.
// File file = new File(Environment.getExternalStorageDirectory(), dirName);
File file = new File(Environment.getExternalStorageDirectory(), dirName);
if (!file.exists()&&!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
public class TimeThread extends Thread{
@Override
public void run() {
super.run();
do{
try {
Thread.sleep(1000);
Message msg = new Message();
msg.what = msgKey2;
tHandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}while (true);
}
}
private Handler tHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case msgKey2:
long time = System.currentTimeMillis();
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd, HH:mm:ss ");
time1.setText(format.format(date));
break;
default:
break;
}
}
};
/**
* 复制单个文件
* @param oldPath String 原文件路径 如:c:/fqf.txt
* @param newPath String 复制后路径 如:f:/fqf.txt
* @return boolean
*/
public void copyFile(String oldPath, String newPath) {
try {
int bytesum = 0;
int byteread = 0;
File oldfile = new File(oldPath);
if (oldfile.exists()) { //文件存在时
InputStream inStream = new FileInputStream(oldPath); //读入原文件
FileOutputStream fs = new FileOutputStream(newPath);
byte[] buffer = new byte[1444];
int length;
while ( (byteread = inStream.read(buffer)) != -1) {
bytesum += byteread; //字节数 文件大小
System.out.println(bytesum);
fs.write(buffer, 0, byteread);
}
inStream.close();
}
}
catch (Exception e) {
System.out.println("复制单个文件操作出错");
e.printStackTrace();
}
}
}
2.activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_dynamic_linechart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView android:layout_height="34dp" android:layout_width="match_parent" android:text="时间" android:gravity="center" android:id="@+id/tv_time1"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView android:layout_height="wrap_content" android:layout_width="119dp" android:text="X:" android:id="@+id/valueX"/>
<TextView android:layout_height="wrap_content" android:layout_width="129dp" android:text="Y:" android:id="@+id/valueY"/>
<TextView android:layout_height="wrap_content" android:layout_width="123dp" android:text="Z:" android:id="@+id/valueZ" />
</LinearLayout>
<Button
android:id="@+id/btn_open"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="打开文件默认存储位置" />
<Button
android:id="@+id/btn_open1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择文件存储位置" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Switch
android:id="@+id/isSave"
android:layout_width="wrap_content"
android:layout_height="51dp"
android:text="数据保存"
/>
<Switch
android:id="@+id/switchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="开始测试" />
<Button
android:id="@+id/restart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#f5f5f5"
android:onClick="restart"
android:text="重新绘图"
android:textColor="#333" />
</LinearLayout>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="247dp" />
<TextView
android:id="@+id/changePath"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" " />
</LinearLayout>
</ScrollView>
3.FileStorage.java
package com.filebrowser.Storage;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import com.filebrowser.FileBrowserActivity;
import com.filebrowser.MainActivity;
import static com.github.mikephil.charting.charts.Chart.LOG_TAG;
/**
* Created by ty on 2018/11/23.
* 保存数据到文件
*/
public class FileStorage {
/**
* 新建文件对象
*/
private File file;
public FileStorage(String filePath){
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);//当前时间获取,作为txt文档的名字
File dir =getAlbumStorageDir(filePath);
file = new File(dir,sdf.format(d)+".txt");
}
/**
* 获取文件夹,如果不存在就创建
* @param dirName 文件夹名称
* @return File file 文件夹File对象
*/
private File getAlbumStorageDir(String dirName) {
// Get the directory for the user's public pictures directory.
// File file = new File(Environment.getExternalStorageDirectory(), dirName);
File file = new File(Environment.getExternalStorageDirectory(), dirName);
if (!file.exists()&&!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
/**
* 对文件添加数据
* @param data 数据
*/
public void addData(List data){
try{
String string="";
for (int i = 0; i <data.size() ; i++) {
String tmp=(String) data.get(i);
for(int j=0;j<(int)(Math.random()*10);j++)
string+=tmp+"\n";
}
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(string.getBytes());
outputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
public List getData(){
return new ArrayList();
}
/**
* 获取文件对象
* @return file 文件对象
*/
public File getFile(){
return file;
}
}
4.ChartView.java
import android.graphics.Color;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.data.*;
import java.util.ArrayList;
/**
** Created by Tll on 2018/11/19
*/
public class ChartView {
private LineChart chart;
private ArrayList<ILineDataSet> dataSets=new ArrayList<ILineDataSet>();
/**
* 构造函数
* @param mChart 线性图表
*/
ChartView(LineChart mChart){
chart=mChart;
init();
}
/**
* 初始化X轴
*/
private void initX(XAxis xAxis){
//设置X轴显示的位置
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
//设置X轴的最小值
xAxis.setAxisMinimum(0f);
//关闭X轴的网格线
xAxis.setDrawGridLines(false);
}
/**
* 初始化Y轴设置
*/
private void initY(YAxis yAxis){
}
/**
* 初始化设置
*/
private void init(){
//可拖拽
chart.setDragEnabled(true);
//不显示圆点
// chart.setDrawCircles(false);
//可缩放
chart.setScaleEnabled(true);
//设置背景颜色,和APP背景融合
chart.setBackgroundColor(Color.parseColor("#fafafa"));
initX(chart.getXAxis());
initY(chart.getAxisLeft());
initLines();
}
/**
* 初始化三条折线
*/
private void initLines(){
//X方向
createNewLine("#607d8b","X");
//Y方向
createNewLine("#e91e63","Y");
//Z方向
createNewLine("#673ab7","Z");
LineData data = new LineData(dataSets);
data.setValueTextColor(Color.GRAY);
chart.setData(data);
//刷新图表
chart.invalidate();
}
/**
* 新建一条折线
* @param lineColor 折线的背景颜色
*/
public void createNewLine(String lineColor,String lineLabel){
ArrayList<Entry> yValue = new ArrayList<>();
yValue.add(new Entry(0,0));
final LineDataSet dataSet=new LineDataSet(yValue,lineLabel);
dataSet.setColor(Color.parseColor(lineColor));
//不显示圆点
dataSet.setDrawCircles(false);
//不显示点上数值
dataSet.setDrawValues(false);
dataSet.setLineWidth(2f);
dataSets.add(dataSet);
}
/**
* 追加数据
* @param val 数据
* @param line 折线id
*/
public void addData(float val,int line){
//获取所有的图标数据
LineData data =chart.getData();
//获取指定折线id的数据
ILineDataSet dataSet=data.getDataSetByIndex(line);
//获取最后一个X轴的坐标
int lastXIndex=dataSet.getEntryCount();
//追加数据
data.addEntry(new Entry(lastXIndex,val),line);
//通知线性表数据已经更新
chart.notifyDataSetChanged();
//设置X轴的显示间隔,数据滚动更新
chart.setVisibleXRange(0,5999);
//移动到最后一个X轴的位置
chart.moveViewToX(lastXIndex);
}
public LineData getData(){
return chart.getData();
}
public LineChart getChart(){
return chart;
}
public ArrayList<ILineDataSet> getDataSets(){
return dataSets;
}
public void reset(){
chart.removeAllViews();
}
}
5.FileBrowserActivity.java
package com.filebrowser;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.filebrowser.Storage.FileStorage;
import java.io.File;
public class FileBrowserActivity extends Activity implements
View.OnClickListener, MyAdapter.FileSelectListener {
private TextView curPathTextView;
private String rootPath = "";
private MyAdapter listAdapter;
//初始化进入的目录,默认目录
private String filePath = "";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file_browser_acitivity);
initView();
//跟目录
rootPath = getIntent().getStringExtra("rootPath");
//指定文件夹
filePath = getIntent().getStringExtra("path");
curPathTextView.setText(filePath);
filePath = filePath.isEmpty() ? rootPath : filePath;
View layoutFileSelectList = findViewById(R.id.layoutFileSelectList);
listAdapter = new MyAdapter(layoutFileSelectList, rootPath, filePath);
listAdapter.setOnFileSelectListener(this);
findViewById(R.id.btnSure).setOnClickListener(this);
findViewById(R.id.btnCancel).setOnClickListener(this);
}
@Override
public void finish() {
Intent intent = new Intent();
intent.putExtra("file", filePath);
setResult(RESULT_OK, intent);
super.finish();
}
private void initView() {
curPathTextView = (TextView) findViewById(R.id.curPath);
}
@Override
public void onFileSelect(File selectedFile) {
filePath = selectedFile.getPath();
}
@Override
public void onDirSelect(File selectedDir) {
filePath = selectedDir.getPath();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnSure:
finish();
break;
case R.id.btnCancel:
filePath ="";
finish();
break;
default:
break;
}
}
}
6.MyAdapter.java
package com.filebrowser;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class MyAdapter extends BaseAdapter implements View.OnClickListener, AdapterView.OnItemClickListener {
private String rootPath;
private LayoutInflater mInflater;
private Bitmap mIcon3;
private Bitmap mIcon4;
private List<File> fileList;
private View header;
private View layoutReturnRoot;
private View layoutReturnPre;
private TextView curPathTextView;
private String suffix = "";
private String currentDirPath;
private FileSelectListener listener;
public MyAdapter(View fileSelectListView, String rootPath, String defaultPath) {
this.rootPath = rootPath;
Context context = fileSelectListView.getContext();
mInflater = LayoutInflater.from(context);
mIcon3 = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_fodler);
mIcon4 = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_file);
curPathTextView = (TextView) fileSelectListView.findViewById(R.id.curPath);
header = fileSelectListView.findViewById(R.id.layoutFileListHeader);
layoutReturnRoot = fileSelectListView.findViewById(R.id.layoutReturnRoot);
layoutReturnPre = fileSelectListView.findViewById(R.id.layoutReturnPre);
layoutReturnRoot.setOnClickListener(this);
layoutReturnPre.setOnClickListener(this);
if (defaultPath != null && !defaultPath.isEmpty()) {
getFileDir(defaultPath);
} else {
getFileDir(rootPath);
}
ListView listView = (ListView) fileSelectListView.findViewById(R.id.list);
listView.setAdapter(this);
listView.setOnItemClickListener(this);
}
private class ViewHolder {
TextView text;
ImageView icon;
}
public interface FileSelectListener {
void onFileSelect(File selectedFile);
void onDirSelect(File selectedDir);
}
public void setOnFileSelectListener(FileSelectListener listener) {
this.listener = listener;
}
/**
* 获取所选文件路径下的所有文件,并且更新到listview中
*/
private void getFileDir(String filePath) {
File file = new File(filePath);
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isFile()&&!suffix.isEmpty()){
return pathname.getName().endsWith(suffix);
}
return true;
}
});
fileList = Arrays.asList(files);
//按名称排序
Collections.sort(fileList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if (o1.isFile() && o2.isDirectory())
return 1;
if (o1.isDirectory() && o2.isFile())
return -1;
return o1.getName().compareTo(o2.getName());
}
});
if (header != null) {
header.setVisibility(filePath.equals(rootPath) ? View.GONE : View.VISIBLE);
}
notifyDataSetChanged();
if (curPathTextView != null) {
curPathTextView.setText(filePath);
}
currentDirPath = filePath;
if (listener!=null){
listener.onDirSelect(file);
}
}
public int getCount() {
return fileList.size();
}
public Object getItem(int position) {
return fileList.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.file_item, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
File file = fileList.get(position);
holder.text.setText(file.getName());
if (file.isDirectory()) {
holder.icon.setImageBitmap(mIcon3);
} else {
holder.icon.setImageBitmap(mIcon4);
}
return convertView;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
File file = fileList.get(position);
if (file.isDirectory()) {
getFileDir(file.getPath());
} else {
if (listener!=null){
listener.onFileSelect(file);
}
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.layoutReturnRoot) {
getFileDir(rootPath);
} else if (v.getId() == R.id.layoutReturnPre) {
getFileDir(new File(currentDirPath).getParent());
}
}
7.MyService.java
package com.filebrowser;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.CompoundButton;
import android.widget.Switch;
import static com.filebrowser.R.id.switchButton;
/**
* Created by Tll on 2018/11/25.
*/
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
System.out.println("创建服务");
}
@Override
public int onStartCommand(Intent intent,int flags, int startId) {
System.out.println("启动服务..."); //这里实现服务的核心业务
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
System.out.println("停止服务");
super.onDestroy();
}
}
8.layout_file_select_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/white"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/layoutFileSelectList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:orientation="vertical"
android:showDividers="middle|end">
<TextView
android:id="@+id/curPath"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:paddingLeft="10dp"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/layoutFileListHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/shape_divider_line"
android:showDividers="beginning|middle|end"
android:orientation="vertical">
<LinearLayout
android:id="@+id/layoutReturnRoot"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="10dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_return_root"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/icon_back">
</ImageView>
<TextView
android:id="@+id/tv_return_root"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:text="@string/ReturnRootDir"
android:textSize="16sp">
</TextView>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutReturnPre"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="10dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_return_pre"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/icon_back02">
</ImageView>
<TextView
android:id="@+id/tv_return_pre"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:text="@string/ReturnPreDir"
android:textSize="16sp">
</TextView>
</LinearLayout>
</LinearLayout>
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
</LinearLayout>
布局如下: