Android基础技术核心归纳(三)
转载请声明出处:http://blog.csdn.net/andrexpert/article/details/77895123
Android Java 数据结构
Android基础技术核心归纳(一) Java基础技术核心归纳(一) 数据结构基础知识核心归纳(一)
Android基础技术核心归纳(二) Java基础技术核心归纳(二) 数据结构基础知识核心归纳(二)
Android基础技术核心归纳(三) Java基础技术核心归纳(三) 数据结构基础知识核心归纳(三)
Android基础技术核心归纳(四) Java基础技术核心归纳(四)
不知不觉又是一年的9月,今天跟一个师弟聊天,谈到了他现在面试的一些情况,突然想起自己当年也是这么走过来的,顿时感慨良多。Android/Java经验汇总系列文章,是当初自己毕业时笔试、面试和项目开发中相关的总结,虽然不是很高深的东西,也没有归纳得很全面,但是对Android、算法、Java把握个大概还是没问题,今天特意将这些文章放出来,希望能够对看到这个系列文章的毕业生朋友一点帮助吧。当然,由于受当初知识面的限制,归纳得可能不是很准确,若有疑问就留言吧,我就不细看了。
1.JNI与NDK工作原理?
一、JNIAndroid Framework由基于Java语言的Java层与基于C/C++语言的C/C++层组成,在某些情况下,为了将Java(上层)与C/C++(底层)有机地联系起来,使得他们相互协调,共同完成某些任务,Android引入了Java本地接口(JNI,Java Native interface),它允许Java代码与基于C/C++编写的应用程序、模块和库进行交互操作。在Android Framework中,借助JNI综合了Java语言与C/C++等本地语言的优点,使得开发者既可以利用Java语言跨平台、类库丰富、开发便捷等特点,又可以利用本地语言开发运行效率更高、更健壮、更安全的程序。
(1)使用JNI几种情况
■注重处理速度(栈、堆速度区别)
与本地代码(C/C++等)相比,Java代码的执行速度要慢一些。如果对某段程序的执行速度有较高的要求,建议使用C/C++编写代码。而后在Java中通过JNI调用基于C/C++编写的部分,常常能够获得很快的运行速度。
■硬件控制
为了更好地控制硬件,硬件控制代码通常使用C语言编写。而后借助JNI将其与Java层连接起来,从而实现对硬件的控制。另外,假如搭载Android的设备上安装Android Framework不支持的硬件时,可以使用C语言实现设备的驱动程序,以便对设备进行控制。
■既有C/C++代码的复用
在程序编写过程中,常常会使用一些已经编写好的C/C++代码,既提高了编程效率,又确保了程序的安全性与健壮性,在复用这些C/C++代码时,就要通过JNI来实现。
■防止被反编译,提高app安全性
(2)JNI的基本原理
在Java代码中通过JNI调用C函数,大概步骤如下:
第一步:编写Java代码
第二步:编译Java代码
第三步:生成C语言头文件
第四步:编写C代码
第五步:生成C共享库(对于Android,使用NDK实现)
第五步:运行Java程序
具体步骤如下:
第一步:编写Java代码
若想在java代码中通过JNI调用C函数,需要在Java类中声明本地方法,所谓本地方法,即在Java类代码中使用"native"关键字声明的方法,该方法与用C/C++编写的JNI本地函数相对应。"native"关键字告知Java编译器,在Java带有该关键字的方法只是声明,具体由C/C++等其他语言编写实现。
class HelloJNI{第二步:编译Java代码
//本地方法声明
native void printHello();
native void printString(String str);
//加载C运行库
//static静态代码块,程序运行时加载且只加载一次
static{
System.loadLibrary("hellojni");
}
public static void main(String args[]){
//实例化一个HelloJNI对象,用于调用本地方法(即调用使用C语言编写的JNI本地函数)
HelloJNI mJNI = new HelloJNI();
mJNI.printHello();
mJNI.printString("My name is Jiangdongguo.");
}
}
使用Java编译器(javac)编译HelloJNI.java,生成HelloJNI.class文件。需要注意的是,此时如果直接执行编译好的字节码文件(.class),就会抛出异常(执行java HelloJNI ,错误: 找不到或无法加载主类 HelloJNI)。那是因为由于尚未创建加载到java代码中的hellojni.dll文件(Android中为hellojni.so文件),无法找到Java虚拟机要加载的C运行库,所以Java程序运行时抛出无法找到hellojni.dll文件的异常。
第三步:生成C语言头文件HelloJNI.h
程序运行时,Java虚拟机会在加载的本地运行库中查找与Java本地方法相匹配的C函数,并生成映射表,而后将Java本地方法与C函数链接在一起。使用函数原型可实现本地方法到本地库函数的映射,函数原型存在于C/C++头文件的,Java提供了javah工具(位于<JDK_HOME>\bin目录)来生成包含函数原型的C/C++头文件:
javah -classpath (.class文件所在的目录路径) <包含以native关键字声明的方法的Java类名称>
C语言头文件HelloJNI.h代码如下:
JNIEXPORT、JNICALL都是JNI的关键字,表示此函数要被JNI调用,函数原型中必须有这两个关键字,JNI才能正常调用本地库函数。Java_HelloJNI_printHello为函数原型的函数名,JNI支持的函数命令形式为"Java_类名_本地方法名",通过函数命名即可推断出JNI本地函数与哪个Java类的本地方法向对应。其中,函数原型参数说明:
>JNIEnv *:JNI接口的指针,用于调用JNI表中的各种JNI函数(JNI中提供的基本函数集);
>jobject:是JNI提供的Java本地类型,用来在C代码中访问Java对象。参数中保存者调用本地方法的对象的一个引用,如jobject中保存着上例中对mJNI对象的引用。
第四步:编写C/C++代码
第五部:生成C共享库
Windows的C/C++共享库以.dll为后缀,使用Mircosoftware Visual C++ 的Command Prompt来构建;Linux的C/C++共享库以.so为后缀,使用Android NDK来构建。其中,Command Prompt构建命令:
cl -I"<JDK_HOME>\include" -I"<JDK_HOME>\include\win32" -LD hellocjni.c -hellojni.dll
其中,<JDK_HOME>为C:\Program Files\Java\jdk1.8.0_20.
最后,执行java HelloJNI,运行HelloJNI类,查看结果。
二.Android NDK
Android NDK(Native Development Kit)是一套允许开发人员将C/C++实现的本地代码嵌入到Android应用程序的开发包,然后由JNI实现Java层与C/C++层之间的交互。众所周知,Android应用程序运行在Dalvid虚拟机上,而NDK允许开发人员将Android应用程序中的部分功能用C/C++语言实现,并将这部分C/C++代码编译成可直接运行在Android平台上的本地代码,即绕过Dalvid虚拟机,直接在Android平台上运行。这些本地代码以动态链接库(.so)的形式存在,NDK的这个特性既有利于代码的重用(动态链接库可以被多个Android应用程序使用),也可以在某种程度上提高程序的运行效率。
1.NDK提供的内容(特性)
(1)提供了一套工具集,这套工具集可以将C/C++源代码生成本地代码;
NDK提供了一系列的工具,帮助开发者快速开发 C (或 C++ )的动态库,并能自动将 so 和 java 应用一起打包成 apk。
(2)NDK 提供了一份稳定的用于定义NDK接口的C头文件(*.h)和实现这些接口的库文件;
(3)NDK提供了一套编译系统,可以通过非常的少的配置生成目标文件。
NDK集成了交叉编译器,并提供了相应的 mk 文件隔离 CPU 、平台、ABI 等差异,开发人员只需要简单修改 mk 文件(指出 “ 哪些文件需要编译 ” . “ 编译特性要求 ” 等),就可以创建出 NDK可以自动地将 so 和 Java 应用一起打包,极大地减轻了开发人员的打包工作。
(4)从Android 2.3开始支持本地Activity(Native Activity)
虽然在程序中使用NDK可以大大提供运行速度,但使用NDK也会带来很多副作用。例如:
◆使用NDK并不是总会提高应用程序的性能,但却会增加程序的复杂度;
◆使用NDK必须要自己控制内存的分配和释放,这样将无法利用Dalvid虚拟机来管理内存,也会给应用程
序带来很大的风险。
◆无法使用NDK编写完整的Android应用程序,由于NDK只开发了部分接口,NDK 并没有提供各种系统事件处理支持,也没有提供应用程序生命周期维护.
2.NDK适用范围
NDK的出现使"Java+C"的开发方式得到官方支持,NDK 将是 Android 平台支持 C 开发的开端. NDK提供了的开发工具集合,使开发人员可以便捷地开发,发布 C 组件。
(1)需要大幅度提高程序性能(C/C++运行速度比Java要快,且可访问操作系统底层)
(2)保密性较高的情况(Java生成的目标文件很容易被反编译)
3.Eclipse搭建NDK集成开发环境
(1)解压NDK,并在Eclispe中配置NDK路径
com/example/hellondk/MyNativeMethod.java
注意:本地方法声明类需要加载C动态库,通过查看Android.mk文件的LOCAL_MODULE := HelloNDK确定动态库的名称,否则会出现" java.lang.UnsatisfiedLinkError: dlopen failed: library "HelloNDK" not found"异常。
(3)生成C语言头文件com_example_hellondk_MyNativeMethod.h
程序运行时,Java虚拟机会在加载的本地运行库中查找与Java本地方法相匹配的C函数,并生成映射表,而后将Java本地方法与C函数链接在一起。使用函数原型可实现本地方法到本地库函数的映射,函数原型存在于C/C++头文件的,Java提供了javah工具(位于<JDK_HOME>\bin目录)来生成包含函数原型的C/C++头文件:
使用DOS环境切换至Android工程的bin\classes目录下:
javah -classpath <.class文件所在的路径> -jni 包名.类名
将会在当前目录下生成MyNativeMethod.java对应的头文件,如果出现找不到类文件错误,说明是-classpath路径设置错误,可以指定字节码文件的绝对路径javah -classpath E:\Android\AndroidPro
jectNew\HelloNDK\bin\classes -jni com.example.hellondk.MyNativeMethod。
说明:-classpath <path> 用于指定包含本地方法类的字节码文件(.class)所在目录路径 .表示当前路径
-jni 包名.类名 用于生成C语言头文件。
头文件:com_example_hellondk_MyNativeMethod.h
(4)为Android工程添加本地支持,即生成jni目录,并将C语言头文件拷贝到jni目录下
方法:右击Android工程属性->Android tools->add Native Support...
(5)在HelloNDK.cpp中,实现本地函数。
(6)编译工程
当我们编译Android工程,系统为自动在工程目录的libs目录下生成一个armeabi目录,里面包含了所需的C语言动态库"libHelloNDK.so"文件
C动态库位置如下所示:
(7)运行程序,实现Java层调用C/C++库
结果:
4.创建头文件和编译多个源文件
(1)使用ANT自动创建头文件
当工程中包含多个本地方法Java类,也就意味着需要在工程中生成多个头文件。如果向之前那样通过命令终端一条一条的创建的话,显然就比较麻烦。此时,可以通过Android自带的ANT工具,实现一次创建。
首先,在工程的根目录下创建一个xml文件,命名为"build_headers.xml",并以Ant Editor打开
然后,使用快捷键"alt+/",自动添加模板
最后,执行Ant命令,生成头文件
5.面向多种CPU架构编译
如果我们希望"Java+C"应用能够运行在不同的CPU架构上,可以在jni目录新建一个Application.mk文件
添加:APP_ABI := x86 armeabi,即可使应用同时支持X86、arm的CPU架构,它会同时生成X86平台,arm平台所需的C动态库。
6.Windows下搭建Android NDK开发环境
由于Android NDK需要Linux的开发环境,因此在Windows下无法直接使用Android NDK,必须要安装Cygwin用于模拟Linux的开发环境才可以使用Android NDK进行开发。
(1)安装模拟linux开发环境Cygwin(版本:cygwin32-x86);
(2)解压NDK(D:\Android\android-ndk-r10c,NDK版本为android-ndk-r10c.exe)
然后,修改Cygwin环境变量。
方法一:打开cygwin\etc\profile
方法二:打开cygwin\home\Administrator\.bash_profile
添加内容:
ANDROID_NDK_ROOT=/cygdrive/d/Android/android-ndk-r10c
export ANDROID_NDK_ROOT
(3)打开Cygwin终端,输入ndk-build或者输入命令cd /cygdrive/d/android-ndk-r10c以检验NDK环境变量是否安装正确,以下情况说明ndk-build命令正常。
7.Android应用中JNI的调用过程
(1)安装和下载Cygwin,下载 Android NDK
(2)在ndk项目中JNI接口的设计
(3)使用C/C++实现本地方法
(4)JNI生成动态链接库.so文件
(5)将动态链接库复制到java工程,在java工程中调用,运行java工程即可
2.Android5.0新特性
(1)支持多种设备
Android系统可以运行在智能手机、平板电脑、笔记本电脑、智能电视、汽车、智能手表等各种家电电子产品上;
(2)全新的通知中心设计
改进后的通知系统会优先显示对用户来说比较重要的信息,而将不太紧急的内容隐藏起来。用户只需要向下滑动就可以查看全部的通知内容,另外,用户也可在锁屏界面直接查看通知消息;
(3)支持64位ART虚拟机
新系统放弃了之前一直使用的Dalvik虚拟机,改用了ART模式,实现了真正的跨平台编译,在ARM、X86、MIPS等。ART虚拟机编译器在内存占用及应用程序加载时间上进行了大幅提升,支持更大的寄存器,支持新的指令集,提升了内存寻址空间。
(4)Project Volta电池续航改进计划
Project Volta计划增加了新工具可以让开发者能够更容易的找出为何自己的应用程序会对电量产生比较大的影响,同时确保在执行某型任务时将手机电量的影响降至最低。Job Scheduler API则可以让开发者更容易的选择合适的时机触发电量消耗比较高的任务,避免在低电量或未完成充电时更新应用程序。
(5)安全性改进
当设备没有检测到附近有可用的信任设备时,就会启动安全模式防止未授权访问。比如当特定的智能手表出现在Android设备的附近,那么就会直接绕过锁屏界面进行操作。Android Lollipop还默认开启了系统数据加密功能,并且通过SELinux执行应用程序,这就意味着对于恶意软件来说,新系统变得更加安全。
(6)新的API支持,蓝牙4.1、USB Audio、多人分享等其它特性
Android Lollipop还增加了多个新的API支持、蓝牙4.1、USB Audio外接音响及多人分享等功能。其中多人分享功能可以在用户手机丢失的情况下,使用其它Lollipop设备登录账户,从云端下载联系人、日历等资料,并且不影响其它设备的内容。
4.简单介绍下Android四大组件?
1.Activity组件
Activity是Android应用提供了可视化用户界面,用于负责与用户进行交互。一个Android应用可以含有一个或者多个Activity,但只拥有一个main Activity。Android使用栈来管理各个Activity,当一个Activity在最前端时,它位于栈顶;当另一个Activity启动时,原来的Activity被压到栈里,而新的Activity位于栈顶;按返回键时,栈顶的Activity出栈,而位于第二的Activity变成栈顶,得到用户焦点。Activity拥有自己的生命周期,Activity的整个生命从onCreate()到onDestroy(),其中,在onCreate()中设置全局状态或处理其他,在onDestroy()中进行资源的回收和销毁。
(1)Activity的四种状态:
◆活动状态:一个新Activity启动入栈后,处于栈的最顶端,在屏幕最前端并获得焦点,此时它处于可见并可与用户进行交互;
◆暂定状态:当Activity被另一个透明或者Dialog样式的Activity覆盖时的状态。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,所以它仍是可见,但它已经失去了焦点,故不可与用户交互;
◆Stopped状态:Activity不可见。此状态Activity将继续保留在内存中保持当前的所有状态和成员信息,假设系统别的地方需要内存的话,这是它是被回收对象的主要候选。因此,当Activity处于Stopped状态时,一定要保存当前数据和当前的UI状态,否则Activity一旦退出或关闭时,当前的数据和UI状态就丢失了。
◆销毁状态:Activity被杀掉以后或者被启动以前,处于销毁状态。这时Activity已被移出Activity堆栈,需要重新启动才可以显示和使用。
(2)Activity的生命周期
◆可见部分:onStart........>用户界面可见......>onStop
◆焦点部分:onResume.....>.获得焦点,可与用户交互(尽量少的耗时任务)......>onPause
2.Service组件
Service与Activity组件类似,有自己的生命周期,但它只运行在后台且没有交互界面,不能与用户进行交互操作,但可以和其他组件进行交互。需要注意的是,Service不是一个单独的进程或者线程,而是像其他应用对象一样运行在其托管进程的主线程中,若希望在Service执行耗时任务,仍需要新建一个线程来实现。---例如:媒体播放器后台服务。
(1)Service分类
◆本地服务:通过startService()启动,用于应用程序内部;
◆远程服务:通过bindService()启动,用于应用程序之间;
(2)Service的生命周期
◆onCreate():Service的生命周期开始,完成Service的初始化工作;
◆onStart():活动生命周期开始
◆onDestroy():Service的生命周期结束,释放Service所有占用的资源
◆启动方式下的Service生命周期:
BroadcastReceiver(广播接收器)是对发出的广播进行过滤接收并响应的组件,BroadcastReceiver本质是一种全局监听器,用于监听系统全局的广播消息并接收指定的广播,它可以非常方便地实现系统中不同组件之间的通信。BroadcastReceiver自身并不实现图形用户界面,但是当接收到某个广播(通知)后,BroadcastReceiver可以启动Activity,或者启动Service作为响应。
(1)BroadcastReceiver机制
在Android里面有各种各样的广播,比如电池的使用状态,电话的接收和短信的接收都会产生一个广播。应用程序开发者可以通过广播接收器监听这些广播,并作出相应的逻辑处理。
如处理接收到短信的广播:
public class MyBroadcastReceiver extends BroadcastReceiver{另外,采用静态方式为该广播接收器注册
String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
public void onReceive(Context context,Intent intent){
//有新信息广播
if(intent.getAction().equals(SMS_RECEIVED))
{
Toast.make(this,"有新信息",Toast.LENGHT).show();
}
//除此之外,包含地域变换、电量不足、来电信息等
}
}
<receiver android:name = ".MyBroadcastReceiver">(2)BroadcastReceiver生命周期
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
每次广播到来时,会重新创建BroadcastReceiver对象,并且调用onReceiver()方法,执行完以后,该对象被销毁,BroadcastReceiver生命结束。由于广播接收器的生命周期只有十秒左右, 当onReceive()方法在10s内没有执行完毕,Android会认为该程序无响应。所以,在BroadcastReceiver里不能做一些比较耗时的操作,否则会弹出ANR(Application No Response)的对话框。可以Intent一个Service处理耗时任务
4.ContentProvider组件
ContentProvider是应用之间互相访问数据的唯一方法,由于每个Android应用都独立运行在自己的Dalvik虚拟机实例中,如果这些Android应用之间需要实现实时的数据交换,可以使用ContentProvider来存储和检索数据。如果你想使你的应用能够共享数据,需要建立一个自己的ContentProvider。通常与ContentProvider结合使用ContentResolver,一个应用程序使用ContentProvider暴露自己的数据,而另一个应用则通过ContentResolver来访问数据。 一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过该接口来操作该应用程序的内部数据,包括增加数据、删除数据、修改数据、查询数据等。
5.ContentProvider如何实现数据共享?
1.ContentProvider共享数据原理
ContentProvider是不同应用程序之间进行数据交换的标准API。假设A应用通过ContentProvider以某种Uri的形式对外(暴露)提供数据,B应用使用ContentResolver根据A应用提供的Uri获得A应用的authority属性去访问操作指定的数据。ContentResolver访问、修改Uri对应的ContentProvider中的数据过程如下:
ContentResolver对指定Uri执行CRUD等数据操作,但Uri并不是真正的数据中心,因此这些CRUD操作会委托给该Uri对应的ContentProvider来实现。比如当B应用调用ContentResolver的insert()方法是,实际上相当于调用了该Uri对应的ContentProvider(属于A应用)的insert()方法。注:Uri是ContentResolver和ContentProvider进行数据交换的标识。
2.ContentProvider生命周期
ContentProvider不同于Activity存在复杂的声明周期,ContentProvider只有一个onCreate()生命周期方法,当其他应用通过ContentResolver第一次访问该ContentProvider时,onCreate()方法将会被回调,onCreate方法只会被调用一次。
3.使用ContentProvdier交换数据步骤
(1)A应用暴露数据--ContentProvider
◆实现一个ContentProvider子类。该子类需要继承Android提供的ContentProvider基类,并且需要实现query()、insert()、update()和delete()(抽象方法)等方法,这些方法本身不是被A应用调用,而是提供给其他应用来调用;
◆配置ContentProvider。在该ContentProvider供其他应用调用之前,需要向Android系统注册ContentProvider,即在A应用的工程文件AndroidManifest.xml文件中注册这ContentProvider(类似于注册Activity),且注册ContentProvider时需要为它绑定一个Uri;
<provider android:name=".FirsttProvider" //指定该ContentProvider的实现类的类名
android:authorities="com.example.test" //指定该ContentProvider对应的Uri
android:exported="true"/> //指定该ContentProvider为允许其他应用调用
(2)B应用访问(修改)A应用暴露的数据
◆获取ContentResolver实例对象
ContentResolver mContentResolver=getContentResolver();
◆通过实例对象调用ContentResolver的query()、insert()、update()和delete()方法。即指定Uri对应ContentProvider的query()、insert()、update()和delete()方法将要完成的功能。
Uri uri=Uri.parse("content://com.example.test/");
查询:Cursor c=contentResolver.query(uri, null, "query_where", null, null);
插入:ContentValues values=new ContentValues();
values.put("name", "jiangdongguo");
Uri newUri=mContentResolver.insert(uri, values);
更新:ContentValues values=new ContentValues();
values.put("name", "zhongxian");
int count=contentResolver.update(uri, values, "update_where", null);
删除: int count=contentResolver.delete(uri, "delete_where", null);
4.Uri介绍
Uri代表了要操作的数据,Uri主要包含了两个部分信息:
(1)需要操作的ContentProvider;
(2)对ContentProvider中的什么数据进行操作,一个Uri由以下几个部分组成
ContentProvider(内容提供者)的scheme已经由Android所规定,scheme为:content://主机名(或称Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
a.要操作person表中id为10的记录,path可为:/person/10
b.要操作person表中id为10的记录的name字段,path可为:/person/10/name
c.要操作person表中的所有记录,path可为:/person
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://com.example.test/person");
6.Android SDK支持哪几种数据存储技术?
Android SDK提供了5种数据存储技术:
(1)使用SharedPreferences存储数据;
(2)文件存储数据;
(3)SQLite数据库存储数据;
(4)使用ContentProvider存储数据;
(5)网络存储数据;
1.SharedPreferences
(1)数据存储原理
SharedPreferences用于保存key-value类型的数据,类似于配置信息格式的数据,以XML格式保存在手机内存(内部存储器)/data/data/<package_name>/shared_prefs/目录下,适合少量数据的保存。Android提供SharedPreferneces接口实现读取应用程序的配置数据,由于SharedPreferences本身没有提供写入数据的能力,则通过其内部接口Editor来实现数据的写入、删除,其中,SharedPreferences调用edit()方法获取它对应的Editor对象。XML文件格式如下:
<?xml version=‘1.0’ encoding='utf-8' standalone='yes'>
<map>
<int name="age" value="20">
<string name="time">2015年8月1日 11:03:00</string>
</map>
(2)使用SharedPreferences存取类型的数据核心代码
保存key-value对需要执行一个文件名,然后使用putXxx的方法指定key和value
具体步骤如下:
◆使用Context.getSharedPreferences方法获取当前应用的SharedPreferences对象,其中存储key-value的文件的名称和权限由Context.getSharedPreferences(String name,int mode)方法指定;
Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。
Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。
Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写
◆SharedPreferences.Editor clear():清空SharedPreferences里所有数据
◆使用SharedPreferences.edit方法获取SharedPreferences.Editor对象;
◆通过SharedPreferences.Editor接口的putXxx(String key , xxx value)方法保存key-value对,其中xxx 可以是boolean,float,int等各种基本类型据。
◆通过boolean commit方法提交要保存的key-value对。
◆SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项
注:commit方法相当于数据库事物中的提交操作,只有在事件结束后进行提交,才会将数据真正保存在数据库中。
>>>>>>>>>>>>>>>从test.xml文件中读取key-value对<<<<<<<<<<<<<<<<<(3)使用IO流读取SharedPreferences对应的XML文件
String XML_NAME="test";
SharedPreferences mSharedPreferences //只允许本应用读写
=getSharedPreferences(XML_NAME,Context.MODE_PRIVATE);
String nameValue = mSharedPreferences .getString("name",""); //如果不存在就返回空
int ageValue = mSharedPreferences .getInt("age",-1);
>>>>>>>>>>>>>>向test.xml文件中写入key-value对<<<<<<<<<<<<<<<
String XML_NAME="test";
SharedPreferences mSharedPreferences //只允许本应用读写
=getSharedPreferences(XML_NAME,Context.MODE_PRIVATE);
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.put("name","jiangdongguo");
editor.put("age",26);
editor.commit();
一般来说,SharedPreferences会将数据文件保存在手机内存(内部存储器)中,文件存储路径为data/data/packagename/shared_prefs。由于未获取root权限的Android手机不允许当前应用程序访问其他应用程序的内部存储路径。为了通用,只能在当前应用程序中使用文件流读写SharedPreferences数据文件,核心代码如下:
String path = Evironment.getDataDirectory() //获取Shared{references文件保存的目录SharedPreferences原则上只能将数据文件保存在当前应用私有的shared_prefs目录中,且由于SharedPreferences未提供改变文件保存路径,可以利用Java反射技术来改变文件的存储路径,将test.xml文件保存到SD卡中。
.getAbsolutePath()+"/data"+getPackageName()+"/shared_prefs/test.xml";
try{
FileInputStream fis = new FileInputStream(path); //打开文件输入流
InputStreamReader isr = new InputStreamReader(fis);
BufferedRead br = new BufferedReader(isr);
String contentFromXMl=br.readLine(); //返回SharedPreferences文件中内容
}catch(Exception e){
}
(4)SharedPreferences文件可以保存图像数据吗?
SharedPreferences文件安可以保存图像数据,但需要将图像(二进制数据)转换成字符串再保存到SharedPreferences文件中。其中将二进制数据转换成字符串的编码格式采用Base64。不过要注意,由于保存和读取过程需要进行编码和解码,所以并不建议在SharedPreferences文件中保存尺寸过大的图像数据或其他二进制数据。
(5) 适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口 令密码等
SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。
2.流文件存储
核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选:
MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可 以使用Context.MODE_APPEND
MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;
MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。
除此之外,Context还提供了如下几个重要的方法:
getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录
File getFilesDir():获取该应用程序的数据文件夹得绝对路径
String[] fileList():返回该应用数据文件夹的全部文件
应用程序的数据文件默认保存在/data/data/packageNmae/files目录下,打开文件输入流和打开文件输出流核心代码如下:
◆打开文件输入流
try{◆以追加模式打开文件输出流
FileInputStrean fis = new FileInputFileStream("test.bin");
byte[] buffer = new byte[1024];
int hasHead = 0;
StringBuilder mStringBuilder = new StringBuilder("");
//从文件中读取buffer字节数到byte数组,返回-1说明到文件末尾
//否则,返回实际读到的字节数
while((hasHead = fis.read(buffer))>0)
{
mStringBuilder.append(new String(buffer,0,hasHead));
}
fis.close(); //关闭文件输入流
return mStringBuilder.toString();
}catch(Exception e){
e.getMessage();
}
try{(2)Android SDK支持哪些获取文件输入输出流的方式?
FileOutputStream fos = new FileOutputStream("test.bin",MODE_APPEND);
PrintStream ps = new PrintStream(fos); //将FileOutputStream包装成PrintStream
ps.println("您好,中国"); //将内容写入输出流文件中
ps.close();
}catch(IOException e){
e.getMessage();
}
◆直接创建FileOutputStream和FileInputStream
try{◆使用Context.openFileOutput和Context.openFileInput方法分别获取用于写文件和读文件的OutputStream、InputStream.
//向文件中写入内容的代码(/data/data/packageName/test.txt)
FileOutputStream fos = new FileOutputStream("test.txt");
fos.close();
//读取文件内容
FileInputStream fis = new FileInputStream("test.txt");
fis.close();
}catch(IOException e){
e.getMessage();
}
try{(3)读写SD卡上的文件
//读取文件内容(/data/data/packageName/test.txt)
OutputStream os = new openFileOutput("test.txt",Activity.MODE_PRIVATE);
os.close();
//向文件写入内容
InputStream is = new openInputStream("test.txt");
is.close();
}catch(IOException e){
e.getMessage();
}
当程序通过Context的openFileInput或openFileOutput来打开文件输入流、输出流时,程序所打开的都是应用程序的数据文件夹里的文件,这样所存储的文件大小可能比较有限。可以通过将数据文件保存到SD卡,SD卡大大扩充了手机的存储能力。读写SD卡上的文件步骤:
◆调用Environment的getExternalStorageState()方法判断手机上是否插入了SD卡,并且应用程序具有读写SD卡的权限,代码如下:
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
注:如果手机已插入SD卡,且应用程序具有读写SD卡的能力,将返回true.
◆调用Environment的getExternalStorageDirectory()方法来获取外部存储器,即SD卡的根目录;
◆使用FileInputStream、FileOutputStream、FileReader或FileWriter读、写SD卡里的文件。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 读取SD卡文件 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<3.SQLite数据库
try{
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File sdcardDir = Environment.getExternalStorageDirectory(); //获取SD卡对应的存储目录
//获取指定文件对应的输入流
FileInputStream fis = new FileInputStream(sdcardDir.getCanonicalPath()+"/file.txt");
//将指定输入流包装成BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
StringBuider sb = new StringBuilder("");
String line = "";
while((line = br.readLine()) != null){
sb.append(line);
}
br.close(); //关闭资源
return sb.toString();
}
}catch(Exception e){
e.printStackTrace();
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 向SD卡文件写入数据 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
try{
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File sdcardDir = Environment.getExternalStorageDirectory(); //获取SD卡对应的存储目录
//获取指定文件对应的输入流
File targeFile = new File(sdcardDir.getCanonicalPath()+"/file.txt");
RandomAccessFile raf = new RandomAccessFile(targeFile,"rw");
raf.seek(targeFile.length); //将文件记录的指针移动到最后
raf.write("Hello World".getBytes()); //将"Hello World"写入到文本末尾
raf.close(); //关闭资源
}
}catch(Exception e){
e.printStackTrace();
}
SQLite是一个开源的轻量级关系型数据库,专门适用于资源有限的设备上存储适量的数据。它支持SQL语言 ,SQLite本身就是一个文件,因此只需占用很少的内存就能获得很好的性能,使用Cursor实现随机访问,具有查询方便、快速的特点(相对于普通文件)。 现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据。
7.android中的动画有哪几类,它们的特点和区别是什么?
两种,一种是Tween(补间)动画、还有一种是Frame(帧)动画。Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;另一种Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。
8.请说出Android支持哪些访问HTTP资源的方式?
一般可以使用HTTP API直接访问HTTP资源,但也可以使用Socket发送HTTP请求。Android SDK支持两种直接访问HTTP资源的方式:DefaultHttpClient和HttpURLConnection。这两种方式都封装了HTTP请求,其中DefaultHttpClient通过HttpGet和HttpPost分别封装GET和POST请求。HttpURLConnection通过URL的构造方法指定HTTP资源地址,然后通过URL.openConnection方法获取HttpURLConnection对象。
1.使用DefaultHttpClient访问HTTP资源
(1)GET请求
Private staic final String URL_PATH="http://10.0.2.2:8080/login/LoginServlet";(2)POST请求
String urlStr=URL_PATH+"?username="+name+"&psd="+psd; //指定URL路径并封装数据
HttpGet mHttpGet=new HttpGet(urlStr);
HttpClient httpClient = new DefaultHttpClient();
HttpResponse mHttpResponse = mHttpClient .execute(mHttpGet); //发送POST请求
//c.判断服务器响应状态,并解析服务响应的数据(响应实体中)
//状态码200
if(mHttpResponse .getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity resEntity = response.getEntity();
InputStream is = resEntity.getContent(); //获得实体中的数据输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String readLine,result;
while((readLine = br.readLine()) != null )
{
result=result+readLine;
}
br.close(); //关闭资源
is.close();
}
}catch(IOException e){
e.printStackTrace();
}
JSONObject json = new JSONObject(); json.put("username",name); json.put("psd",psd); String jsonString = json.toString(); StringEntity entityReq = new StringEntity(jsonString); // StringEntity是HttpEntity实体子类 entityReq.setContentEncoding("UTF-8"); // 设置JSON格式字符串编码方式为UTF-8 Private staic final String URL_PATH="http://10.0.2.2:8080/login/LoginServlet"; HttpPost mHttpPost = new HttpPost(URL_PATH); httpPost.setEntity(entityReq); //b.发送POST请求到服务器 HttpClient mHttpClient = new DefaultHttpClient(); HttpResponse mHttpResponse = mHttpClient .execute(mHttpPost ); //发送POST请求 httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 6000); httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 6000); //6s读取超时 ******************(二)封装普通参数的POST请求 *************** //a.创建参数列表(数据),并将请求路径和实体封装到POST请求 Private staic final String URL_PATH="http://10.0.2.2:8080/login/LoginServlet"; HttpPost mHttpPost = new HttpPost(URL_PATH); NameValuePair param1 = new BasicNameValuePair("username",name); NameValuePair param2=new BasicNameValuePair("psd",psd); List<NameValuePair> paramsList = new ArrayList<NameValuePair>(); paramsList.add(param1); paramsList.add(param2); try{ HttpEntity mHttpEntity = new UrlEncodedFormEntity(paramsList,"GBK"); //创建请求实体 mHttpPost. setEntity(mHttpEntity ); //将实体绑定到POTS请求中、 ******************发送请求,解析服务器响应数据************ //b.发送POST请求到服务器 HttpClient mHttpClient = new DefaultHttpClient(); HttpResponse mHttpResponse = mHttpClient .execute(mHttpPost ); //发送POST请求 //c.判断服务器响应状态,并解析服务响应的数据(响应实体中) //状态码200 if(mHttpResponse .getStatusLine().getStatusCode() == HttpStatus.SC_OK) { HttpEntity resEntity = response.getEntity(); InputStream is = resEntity.getContent(); //获得实体中的数据输入流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String readLine,result; while((readLine = br.readLine()) != null ) { result=result+readLine; } br.close(); //关闭资源 is.close(); } }catch(IOException e){ e.printStackTrace(); }2.使用HttpURLConnection访问HTTP资源
HttpURLConnection类是一种重要的访问HTTP资源的方式。HttpURLConnection类具有完全的访问能力,可以取代HttpGet和HttpPost类。使用HttpURLConnection访问HTTP资源可以使用如下几步:
(1)使用java.net.URL封装的HTTP资源的URL,并使用openConnection方法获得HttpURLConnection对象
URL url = new URL("http://www.baidu.com");
HttpURLConnection httpURLConnection = (HttpURLConnection)url.openConnection();
(2)设置请求方法,如GET、POST(必须大写)
httpURLConnection.setRequestMethod("POST");
(3)设置下载HTTP资源或向服务器上传数据权限
httpURLConnection.setDoInput(true); //允许下载HTTP资源
httpURLConnection.setDoOutput(true); //允许向服务器上传数据
httpURLConnection.setUseCaches(false);//禁止使用缓存
(4)设置HTTP请求头,如设置Charset请求头为UTF-8;"Connection"请求头为Keep-Alive;
httpURLConnection.setRequestProperty("Charset","UTF-8"); //字符集为UTF-8
httpURLConnection.setRequestProperty("Connection","Keep-Alive"); //保持连接
(5)上传数据与下载数据。这一步是对HTTP资源的读写操作,也就是通过InputStream和OutpuStream读取和写入数据。
InputStream is = httpURLConnection.getInputStream();
OutputStream os =httpURLConnection.getOutputStream();
(6)关闭输入输出流
is.close();
os.close();
模拟Web页面向服务端上传文件时,每一个文件的开头需要有一个分界符。分界符需要同时通过HTTP请求头指定和传输数据中指定,服务端程序先会从HTTP请求头中获取分界符,以便识别上传数据中的分界符。
httpURLConnection.setRequestProperty("Content-Type","multipart/form-
data;boundary="+boundary);
其中,上传文件的HTTP请求信息分为如下4部分:
(1)分界符:由两部分组成,即两个连字符"--"和一个任意的字符串(如************123456)
(2)上传文件的相关信息。包括请求参数名、上传文件名、文件类型等,如Content-Disposition:form-data;name="file";filename="abc.jpg"
(3)上传文件的内容。字节流形式
(4)文件上传完成后的结束符(分界符)
举例:
String end = "\r\n";9.GET请求、POST请求、HEAD请求区别?
String boundary = "************123456";
String twoHyphens = "--";
DataOutputStream dos = new DataOutputStream(httpURLConnection.getOutputStream());
//分界符
dos.writeBytes(twoHyphens+boundary+end);
//上传文件的相关信息
dos.writeBytes(“Content-Disposition:form-data;name=\"file\";filename=\"abc.jpg”+end);
dos.writeBytes(end);
//上传文件的内容,字节流形式
FileInputStream fis = new FileInputStream("/sdcard/abc.jpg");
byte[] buffer = new byte[8192];
int count = 0;
while((count = fis.read(buffer)) != -1){
dos.write(buffer,0count);
}
fis.close();
dos.writeBytes(end);
//结束符
dos.writeBytes(twoHyphens+boundary+"--"+end);
dos.flush();
dos.close();
(1)HEAD请求:HEAD请求只是请求消息报头,而不是完整的内容。即其不用传输整个资源内容,就可以得到Request-URL所标识的资源信息,常被用于测试超链接的有效性,是否可以访问以及最近是否更新。
(2)GET请求:GET方法用于获取由Request-URI所标识的资源信息,常见的形式:GET Request-URI
HTTP/1.1,举例:Http://localhost/login.php?username=aa&password=1234
(3)POST请求:POST 请求服务器接收在请求中封装的实体,并将其作为由 Request-Line 中的
Request-URI 所标识的资源的一部分
GET请求与POST请求区别:
◇ GET方式提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST则没有此限制
◇GET方式通过URL提交数据,GET方法参数会显示在地址栏上,数据在URL中可以看到,信息无法保密;而POST方式是将数据放在HTTP请求数据包的实体中(body),对用户不可见。因此,POST方式的安全性要比GET方式高。
◇如果这些数据是中文数据而且是非敏感数据,那么使用 get;如果用户输入的数据不是中文字符而且包含敏感数据,那么还是使用 post为好。
11.什么是HTTPS,Android应用如何访问HTTPS资源?
HTTPS(Secure Hypertext Transfer Protocol,安全超文本传输协议,HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版,HTTP下加入了SSL层,HTTPS的安全基础是SSL,其被广泛应用于万维网上安全敏感的通信,如交易支付等。HTTPS是一个URI scheme(抽象标识符体系),句法类同http:体系,用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口(443)及一个加密/身份验证层(SSL,在HTTP与TCP之间)。
12.Android Socket编程原理
1.TCP/IP协议
TCP/IP通信协议是一种可靠的网络协议,它在通信通信的两端之间建立网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。
(1)IP协议:Internet Protocol, 属于网络层用于提供点对点通信的网络协议 。IP协议负责寻找有效的数据链路将消息从一个主机传送到另一个主机,消息在传递的过程中被分割成一个个小包(IP分组)。IP协议的数据报传送无需建立连接,其只保证计算机能快速地发送和接收数据,因此,数据传输速度快,但由于IP协议提供的不可靠,可能会出现数据分组丢失并且不会重传等问题,这就需要TCP协议来提供可靠并且无差错的通信服务。
(2)TCP协议:Transport Control Protocol,属于传输层用于提供端对端通信的传输控制协议,是一个面向连接的协议。TCP协议是一种端对端的协议,当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接,即用于发送和接收数据的虚拟链路,以实现应用进程之间的逻辑通信。TCP协议负责收集要传送的信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确地还原。TCP协议重发机制:当一个通信实体发送一个消息给另一个通信实体后,需要收到一个通信实体的确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才发送的信息,从而保证了数据报在传输中准确无误。
优点:TCP协议向应用程序需要要建立连接,提供可靠的通信连接,使他能够自动适应网上的各种变化,即使在Internet暂时出现堵塞的情况下,TCP也能够保证通信的可靠。---端到端通信
2.Android C/S编程实现
Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端的通信接口,并通过Socket产生I/O流来进行网络通信。
◆ServerSocket类:用于创建TCP服务器端,并指定服务器网络应用端口,如果主机有多张网卡,还可以为其指定服务器所使用的IP地址。ServerSocket对象用于监听来自客户端的Socket连接,如果连接失败,它将一直处于等待状态(即线程被阻塞)。服务器不应该只接收一个客户端请求,而应该不断地接收来自客户端的所有请求,每接受一个请求就会返回一个与客户端相对应的Socket。
◆Socket类:Socket对象来代表两端的通信接口,并通过Socket产生I/O流来进行网络通信,使用Socket进行通信。每个TCP连接有两个Socket,客户端使用Socket对象来连接到指定服务器。--港口
注释:Socket相当于"港口",数据的交换通过输入输出流实现,相当于"货船"。
(1)服务器端-PC运行的Java应用
为实现在PC机上的一个Java应用,用于建立ServerSocket监听,并使用Socket获取输出流输出。
//TCP通信服务端注意:上面的程序并未把OutputStream流包装成PrintStream,然后使用PrintStream直接输出整个字符串,是因为该服务器端程序运行于PC主机上,当直接使用PrintSeam输出字符串时默认使用系统平台的字符串(GBK)进行编码。但由于客户端是utf8编码,为了保证客户端能正常解析到数据,这里我们指定使用UTF-8字符集进行编码。
public class TcpServer {
public static void main(String[] args) throws IOException
{
/*1.创建一个ServerSocket并指定服务器网络应用端口,用于监听来自客户端Socket*/
ServerSocket mServerSocket=new ServerSocket(30000);
/*2.循环监听不断来自客户端的请求*/
while(true)
{
try{
//每当接收到客户端Socket的请求,服务器端也对应产生一个Socket
Socket mSocket=mServerSocket.accept();
OutputStream os=mSocket.getOutputStream(); //获取服务器端Socket的输出流
os.write("Hello World".getBytes("utf-8"));
}catch(Exception e){
e.printStackTrace();
}finally{
os.close(); //关闭输出流、Socket
mSocket.close();
}
}
}
}
(2)客户端-Android应用
◆TCPClient/src/com.example.tcpapp/TCPTest.java
public class TcpTest extends Activity {◆AndroidManifest.xml修改网络访问权限
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/*创建一个子线程,执行网络通信任务*/
new Thread()
{
@Override
public void run()
{
try {
//1.创建客户端Socket,并指定服务器的IP、Port,与服务器建立连接
Socket mSocket = new Socket(" 172.17.20.4",30000);
mSocket.setSoTimeout(10000); //10s之内无法连接到服务器,判断为超时
等价于:
Socket mSocket1 = new Socket(); //创建一个无连接的socket
mSocket.connect(new InetAddress(" 172.17.20.4",30000),10000);
//2.将Socket对应的输入流包装成BufferedReader
BufferedReader br=new BufferedReader(new InputStreamReader(mSocket
.getInputStream()));
//3.进行普通的IO操作:从socket中取出数据
String line = br.readLine();
等价于:
Scanner scan = new Scanner(mSocket
.getInputStream());
String line = scan.nextLine();
System.out.println(line);
}catch(SocketTimeoutException e){
e.printStackTrace();
}
catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
br.close();
socket.close();
}
}
}.start();
}
}
<uses-permission android:name="android.permission.INTERNET"/>注意:由于建立网络连接、网络通信是不稳定的,它所需要的时间也不确定。如果我们直接在UI线程中建立网络连接、通过网络读取数据可能阻塞UI线程,导致Android应用失去响应。因此,我们启动一条新线程来建立网络连接,并读取数据。