下面主要从系统层面阐述一些重要问题,如内存管理、应用管理、电源管理、系统还原、系统升级、系统配置、系统备份等,从一个更高的视角帮组开发者理解Android的运行。
1.内存管理
在Android终端中,虽然内存的配置已经与功能手机有了明显的不同,不再局限于满足基本的需要,但是有效的内存管理将有助于提升程序运行的效率。
在创建进程时,Dalvik虚拟机会为每个进程分配一定量的堆内存,占用内存较多的程序很容易引起OutOfMemoryError等异常。
另外,在数据交换比较频繁的场景中,多用SQLite来进行缓存。
(1)对象引用
从JDK1.2开始,Java对象的引用被分为强引用(HardReference)、弱引用(WeakReference)、软引用(SoftReference)和虚引用(PhantomReference)等4个级别。
强引用表示即使虚拟机内存“吃紧”抛出了OutOfMemoryError异常,该类型的对象也不会被回收;软引用表示在虚拟机内存“吃紧”抛出OutOfMemoryError异常前,该类型的对象就会被回收;弱引用更适合那些数量不多但体积比较庞大的对象,弱引用对象最容易被回收;虚引用一般没有实际意义,仅观察GC的活动状态,对于测试比较实用,同时必须和引用队列(ReferenceQueue)一起使用。
弱引用、软引用、虚引用均可与引用队列联合使用,当引用的对象被回收时,Dalvik会把该引用对象加入到与之关联的引用队列。
在默认情况下,创建的Java对象均为强引用。创建引用队列的方法:ReferenceQueue
创建一个弱引用,并将对象与引用队列关联的方法:WeakReference
在Java中,有一种特殊的MAP类型即WeakHashMap,在该类型的MAP中,存放了键对象的弱引用,当一个键对象被垃圾回收时,相应的值对象的引用会从MAP中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。
Android在实现上支持以上4种引用和WeakHashMap。
(2)垃圾回收策略
在Android中,通常每个应用占据一个虚拟机,所以Android的垃圾回收是基于应用进行的。
在应用层,通过System.gc()可以调用垃圾回收器回收垃圾。
事实上,在Android的原生代码层,通过引入引用计数机制,Android同样实现了自动垃圾回收,相关的实现位于\frameworks\base\include\utils\RefBase.h中。几乎所有的原生类均继承了RefBase类,RefBase类会维护对象的强引用和弱引用计算,一旦强引用计算为0,对象释放自己,原生代码的垃圾回收机制如下图:
其中维护强引用的指针称为sp(strong pointer),维护弱引用的指针称为wp(weak pointer),sp和wp均依赖于RefBase类,因此只有继承于RefBase类的原生类才具有自动垃圾回收的能力。
1)sp的实现
强引用sp的实现非常简单,技术上主要集中于模板。构建一个强引用指针的过程实际上是对指针m_ptr进行赋值,然后向对象本身添加一个强引用计数。当销毁一个强引用指针时,要将该对象引用计数减1,当强引用计数变为0时,对象才真正销毁。
2)wp的实现
弱引用wp的实现类似于sp,当构建一个wp指正时,实际上是将对象本身的弱引用计数加1。和sp类似,当销毁一个wp指针时,实际上是将对象的弱引用计数减1。
2.应用管理
应用管理主要包括应用程序的启动、销毁及配置等,另外应用程序的数据也是应用管理的一部分。
(1)应用的配置
应用的配置涉及用户权限、版本信息、运行配置、应用标签、应用图标、安装位置等内容。这些属性在frameworks\base\core\res\res\values\目录下的attrs_manifest.xml文件中定义。不恰当的配置往往会导致莫名其妙的异常。
用户权限包括normal、dangerous、signature、signatureOrSystem等4个级别。
版本信息包括versionCode、versionName、minSdkVersion、targetSdkVersion、maxSdkVersion等。其中versionCode和versionName属性主要用于查看应用详情、应用升级场景。minSdkVersion、targetSdkVersion和maxSdkVersion属性用于基于SDK创建的第三方应用。需要注意的是,基于源代码编译出来的系统级应用不能在AndroidManifest.xml文件中,否则可能在启动器中看不到该应用的入口。
运行配置包括sharedUserId、sharedUserLabel、persistent、debuggable、vmSafeMode、exported、process、taskAffinity、allowTaskReparenting、multiprocess、finishOnTaskLaunch、finishOnCloseSystemDialogs、clearTaskOnLaunch、noHistory、alwaysRetainTaskState、stateNotNeeded、excludeFromRecords、authorities、syncable、initOrder等属性。其中sharedUserId属性常被用于和其他应用共享数据;persistent属性可以使应用程序保持在系统整个生命周期内一直运行而不会被系统销毁;debuggable属性表示在应用发布时是否支持调试。
安装位置的配置主要通过installLocation属性进行。在Foryo中,Android开始支持将应用安装到SD卡上,目前Andrioid提供了auto、internalOnly和preferExternal等选项。将应用程序是安装到Flash RAM还是SD卡上,需要考虑铲平的定位和配置的实际情况。如果配置较低,Flash RAM空间有限,将应用安装到SD卡上是个不错的选择,此时需注意的是,系统必需的一些应用不应安装到SD卡上。
对于Andoid应用最重要的组件是Activity,它也有很多的属性需要开发者了解并进行适当配置。其中,重要的属性包括theme、label、launchMode、screenOrientation、configChanges等。
对于launchMode属性,Android定义了standard、singleTop、singleTask、singleInstance等模式。其中standard模式是普通模式,在启动时会创建一个Activity的实例,如果是利用Intent启动的,可以通过设置FLAG_ACTIVITY_NEW_TASK标志位进入该模式;singleTop模式表示如果有可视的同一个Activity实例存在,则复用该实例;singleTask模式表示如果在一个Task中已经运行了该Activity的实例,则复用该实例,通过设置FLAG_ACTIVITY_BROUGHT_TO_FRONT标志位可以利用Intent来进入singleTask模式;singleInstance模式表示该Activity的实例单独运行在一个Task中,如果通过该实例启动一个新的Activity实例,则该新Activity实例会运行在一个新的Task中。 如果没有显式声明,Activity以standard模式启动。
对于configChanges属性,目前Android支持unspecified、landscape、portrait、user、behind、sensor、nosensor等偏好。其中unspecified表示没有明确的偏好,由系统确定屏幕方向;landscape表示横向启动;portrait表示竖向启动,这是最常用的偏好;user表示采用用户当前的偏好;behind表示采用和隐藏在该Activity后的Activity的偏好;sensor表示根据传感器设定偏好;nosensor表示忽略传感器偏好的设定。如果没有显式声明,Activity以当前偏好的屏幕方向启动。
对于configChanges属性,表示该Activity需要处理的配置变化,这些配置包括mcc、mnc、locale、toochscreen、keyboard、keyboardHidden、navigation、orientation、screenLayout、uiMode、fontScale等。
在Android中,触摸屏的类型有undefined、notouch、stylus、finger等,其中stylus表示支持手写,finger表示支持手指触摸。
键盘的类型有undefined、nokeys、qwerty、tewlvekey等。其中qwerty表示QWERTY键盘;twelvekey表示功能手机最常用的12按键的键盘。
导航设备的类型有undefined、nonav、dpad、trackball、wheel等。其中dpad表示采用的是五向键;trackball表示采用的是轨迹球;wheel表示采用的是滚轮。
需要说明的是,配置的变化可能导致Activity重新加载,游戏设计中尤其要注意这一点。有时候还需要针对特定的配置和屏幕方向建立特定的布局。
当应用在Eclipse中编译通过后,需注意在工程的bin目录下的APK文件是Debug模式,如果希望发布Release模式的APK,则需要签名导出。
(2)应用的启动
Android应用的启动模式分为两种,一种是通过启动器(Launcher)启动,另一种是通过Intent消息启动。
如果在通过Intent消息启动前,希望判断欲启动的应用是否安装,目前有两种方法可以检测相关的信息,一种是检测相关的UI组件是否存在,另一种是检测安装包是否存在。
检测UI组件是否存在的方法如下:
Intent intent=new Intent();
intent.setClassName(pluginPackageName, pluginPackageName+".EngineSettings");
PackageManager pm=getPackageManager();
ResolveInfo bestMatch=pm.resolveActivity(intent.PackageManager.MATCH_DEFAULT_ONLY); //检测Activity
检测安装包是否存在的方法同样是基于PackageManager进行的,具体如下:
public abstract PackageInfo getPackageInfo(String packageName, int flags)
如果安装的应用体验不佳,就要进行卸载,卸载的方法如下:
Uri uri=Uri.fromParts("package", packageName, null);
Intent it=new Intent(Intent.ACTION_DELETE, uri); //packageName为包名,比如con.miaozl.apkInstaller
startActivity(it);
另外,通过adb命令也能启动应用,方法如下:
#adb shell am start -n com.miaozl.test/.MainActivity
(3)应用的销毁
应用的销毁目前有3种可行的方法:基于虚拟机的本地方法、基于窗口管理器的方法、基于窗口的生命周期的方法。
1)基于虚拟机的本地方法
在这种方法中,实际上是利用了Linux的进程管理策略,为了销毁一个应用(即进程),可以做如下调用:
android.os.Process.killProcess(android.os.process.myPid());
也可以通过进程正常退出的方式来销毁进程,例如:
System.exit(0); //参数为0,表示正常退出
2)基于窗口管理器的方法
为了通过Android内置的窗口管理器来销毁应用,首先需要获取窗口服务的句柄,方法如下:
ActivityManager am=(ActivityManager)getSystemService(ACTIVITY_SERVICE);
销毁应用的方法有两种,其中一种如下:
am.restartPackage(packagename);
这种方法需要android.permission.RESTART_PACKAGES权限,且在Android API Level为3以上才可以使用。
am.killBackgroundProcesses(packagename);
这种方法在Android API Level为8以上才可以使用。
3)基于窗口的生命周期
采用基于窗口的生命周期的方式来销毁应用是一种策略上的实现,其思路是在最后一个窗口中利用Activity的栈策略将启动该应用的Activity全部销毁,然后在退出最后一个窗口时销毁该窗口,实例如下:
Intent intent=new Intent();
intent.setClass(Android123.this, lastActivityname.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //注意FLAG的选择
startActivity(intent);
3.电源管理
在过去的若干年,计算能力和显示能力都不断提高,但电源技术却一直没有显著提高,这已经成为移动终端发展的最大障碍。事实上,随着人们需求的不断提升,大尺寸的显示屏、耗电量较高的网络计算、丰富用户体验的3D渲染等都极大地消耗着有限的电力供应,因此如何在保证用户体检的前提下,最大限度地延迟系统的运行时间就显得十分重要。电源管理已经成为企业的和兴技术竞争力之一。
在移动终端中,耗电大的3个方面主要是触摸屏、CPU和WiFi。Android的整个电源管理也主要集中在这3个方面。
(1)电源管理框架
Android的电源管理主要是通过唤醒锁和定时器来切换系统状态,整个系统的电源管理框架如下图:
在Android的电源管理框架中,PowerManagerService是核心部分,它和Linux内核的交互是通过power.c来实现的。power.c和内核交互式通过SYS文件来来是实现的。
在PowerManagerService中,通过TimeoutTask实现正常情况下的亮度调整。TimeoutTask的具体使用主要是通过setTimeoutLocked()方法来实现的。
对于屏幕的亮度,目前有SCREEN_BRIGHT、SCREEN_DIM和SCREEN_OFF等3种状态,另外,PowerManagerService还通过mNotificationTask实现了有通知来临时的亮度调整。
应用层的电源管理通常是通过系统的电源管理服务进行的,获取电源管理服务的方法如下:
mPowerManager=(PowerManager)getSystemService(POWER_SERVICE);
在Android 2.2中,增加了对系统重启的支持,当然有相应的权限限制,其实现方法如下:
mPowerManager.reboot(null); //通常设为null
系统重启要求具有android.Manifest.permission.REBOOT权限。另外,通过goToSleep()方法可以让设备休眠一段时间。
(2)应用层策略
1)WiFi策略
WiFi的工作模式可以分为WIFI_MODE_FULL、WIFI_MODE_FULL_HIGH_PERF和WIFI_MODE_SCAN_ONLY等。在WIFI_MODE_FULL模式下,WiFi处于一般工作状态;当需要更高性能时,WiFi处于WIFI_MODE_FULL_HIGH_PERF模式;当WiFi处于WIFI_MODE_SCAN_ONLY模式时,WiFi仅保持激活和接入点扫描功能。
WiFi关于电源管理的代码主要位于WifiService.java中,如果应用程序想在屏幕被关掉后继续使用WiFi,那么可以用acquireWifiLock()来锁住WifiLock,阻止WiFi进入睡眠状态;当听筒程序不再使用WiFi时,需要调用releaseWifiLock()来释放WifiLock,之后WiFi可以进入睡眠状态以节省电源。Android支持获取WifiLock的最大数目为50个。
在默认情况下,当屏幕被关掉以后,如果没有应用程序使用WiFi,WiFi会在2分钟后进入睡眠状态,这主要是为防止频繁改变WiFi的电源模式。;另外,在WifiStateTracker的setPowerMode()方法中也有关于电源管理的实现。
2)LED策略
LED在电源管理方面主要是通过调节亮度来达到省电目的的,设置背光亮度的方法如下:
IPowerManager power=IPowerManager.Stub.asInterface(ServiceManager.getService("power"));
power.setBacklightBrightness(brightness);
亮度的取值范围为0~255,取值为255时为最大亮度。
3)唤醒策略锁
在Android中,上层应用可以利用的一个电源管理策略是唤醒锁(WakeLock),通过设置标志位可以控制屏幕的背光、键盘灯和CPU。创建屏幕背光唤醒锁的方法如下:
mPowerManager=(PowerManager)getSystemService(POWER_SERVICE);
mWakeLock=mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,getClass().getName());
点亮屏幕的方法如下:
mWakeLock.acquire();
恢复屏幕到原来状态的方法如下:
mWakeLock.release(); //注意必须先执行
mWakeLock.acquire();
另外从Android 2.1开始,PowerManager增加了对屏幕是否点亮的判断,方法如下:
public boolean isScreenOn()
除了SCREEN_BRIGHT_WAKE_LOCK标志位外,唤醒锁还支持PARTIAL_WAKE_LOCK、FULL_WAKE_LOCK、SCREEN_GRIGHT_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK和PROXIMITY_SCREEN_OFF_WAKE_LOCK(应用层不可见)等。对于PARTIAL_WAKE_LOCK类型的唤醒锁,CPU会继续运行,即使用户已经按下了电源键也将继续,而对其它类型的唤醒锁,在用户按下电源键后系统将进入睡眠状态。
注意:除非确实需要,否者不建议获取唤醒锁。另外唤醒锁必须成对使用,如果申请后没有释放会造成系统故障,使系统永远无法进入休眠模式。
4.下载管理
在Android中,下载管理在Froyo及以前版本中就存在,但是在Gingerbread中才对开发者开放,其本质上是一个基于HTTP的下载。当下载失败或设备重启时,下载管理会自动重新尝试下载。下载管理要求的权限为android.permission.ACCESS_DOWNLOAD_MANAGER,获得下载服务的方法如下:
DownloadManager mDownloadManager=(DownloadManager)getSystemService(Context.DOWNLOAD_SERVIEC);
(1)GingerBread前的下载
在Gingerbread之前,开发者无法直接调用DownloadManager进行下载,但可以通过向下载管理器中插入数据来触发下载。
1)执行下载
为了执行下载,必须通过ContentValues配置不同的属性,方法如下:
ContentValues values=new ContentValues();
values.put(Downloads.URI, uri); //指定下载地址
values.put(Downloads.COOKIE_DATA, cookie); //设置cookie
values.put(Downloads.VISIBILITY.Downloads.VISIBILITY_HIDDEN); //是否在下载列表中显示
values.put(Downloads.NOTIFICATION_PACKAGE, getPackageName()); //当下载完成时,通知发起下载的应用
values.put(Downloads.NOTIFICATION_CLASS, DownloadCompleteReceiver.calss.getName()); //设置下载完成的广播接收器
values.put(Downloads.DESTINATION, save_path);
values.put(Downloads.TITLE, title);
getContentResolver().insert(Downloads.CONTENT_URI, values);
如果基于SDK进行实现,则由于Downloads类为hide状态,开发者自行将相应的键改为对应的字符串即可。
2)设置代理
在部分场景下,下载可能需要通过代理,Android同样对代理提供了支持,实现代理的方法如下:
values.put(Downloads.PROXY_HOST, "10.0.0.172");
values.put(Downloads.PROXY_PORT, "80");
3)监听任务
有时可能需要关注下载过程,这可以通过监控数据库来实现,具体实现如下:
private class MyContentObserver extends ContentObserver{
public MyContentObserver(){
super(new Handler());
}
public void onChange(boolean selfChange){
handleDownloadsChanged(); //处理下载变化
}
}
4)删除下载记录
删除下载记录的方法如下:
private void deleteHistory(String title){
StringBuilder whereDelete=new StringBuilder(Downloads.TITLE);
whereDelete.append("=");
whereDelete.append("'");
whereDelete.append(str);
whereDelete.append("'");
getContentResolver().delete(Downloads.CONTENT_URI, whereDelete.toString(), null);
}
(2)Gingerbread后的下载
在Gingerbread及以后的版本中,通过DownloadManager可以很容易地实现下载、监控下载状态、处理下载文件等。
1)执行下载
为了下载内容,必须了解URL,然后设置网络、判断是否支持漫游下载、显示下载提醒、判断MIME类型和设置保存位置等,方法如下:
Uri uri=Uri.parse(http://commonsware.com/misc/test.mp4);
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdirs();
Request mRequest=new Request(uri);
mRequest.setAllowedOverRoming(false);
mRequest.setTitle("Demo");
mRequest.setDescription("Something useful . No, really.");
mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "test.mp4")); //设置存储路径
lastDownload=mDownloadManager.enqueue(mRequest); //加入下载队列,并维护下载ID为“lastDownload”
注意:在DownloadManager中,保存位置的设置有很大的局限性。另外,通过DownloadManager还可以设置下载项是否在下载列表中显示,但是在Android3.2中仅Request.VISIBILITY_VISIBLE能正常执行。
2)查看下载队列
查看下载队列的方法如下:
startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));
3)查看下载信息
通过下载ID可以查看该下载项的下载状态、文件大小、时间戳、保持位置等,方法如下:
Cursor c=mDownloadManager.query(new DownloadManager.Query().setFilterById(lastDownload));
if(c!=null){
c.moveToFirst();
Long size=c.getLong(c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADS_SO_FAR);
Long time=c.getLong(c.getColumnIndex(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP));
Long path=c.getSting(c.getColumnIndex(Downloadmanager.COLUMN_COCAL_URI));
int status=c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUE));
}
DownloadManager还支持对下载项的移除和打开,移除的方法为remove(),打开的方法为openDownloadedFile()。
4)处理下载文件
为了处理下载文件,Android支持Action为DownloadManager.ACTION_DOWNLOAD_COMPLETE和DownloadManager.ACTION_NOTIFIVATION_CLICKED等的Intent,这两项分别对下载完成和用户单击下载项进行广播。为了响应下载管理器的Intent事件,开发者必须通过BroadcastReceiver接收广播。
5.系统配置
Android针对不同的场景提供了很多的系统级配置,如运行环境的配置。
(1)系统数据库
在Android的框架层,定义了系统的一些默认设置,主要分为System、Secure、Bookmarks和bluetooth_devices等几部分。相关的文件包括SettingsProvider.java、DatabaseHelper.java、Settings.java、defaults.xml等。系统配置的各组件间的关系如下:
数据主要存储在settings.db数据库中。数据条目以
接入数据库的方法:boolean waitForDebugger=Settings.System.getInt(getContentResolver(), Settings.System.WAIT_FOR_DEBUGGER, 0);
要接入系统数据库,需要android.permission.WRITE_SETTINGS、android.permission.WRITE_SECURE_SETTINFS等权限。
另一部分与硬件密切相关的固定配置数据库存储在init.rc、init.goldfish.rc和编译脚本中,这部分数据通过如下方式调用:
int type=SystemPropeties.getInt("ro.telephony.default_network", RILConstants.PREFERRED_NETWORK_MODE);
这部分数据的存储位置由PROP_PATH_RAMDISK_DEFAULT、PROP_PATH_SYSTEM_BUILD和PROP_PATH_SYSTEM_DEFAULT、PROP_PATH_LOVAL_OVERRIDE等宏进行定义。
在init.rc启动脚本中的系统配置实例如下:
on property:ro.kernel.qemu=1 start adbd
on property:persist.service.adb.enable=1 start adbd
on.property:persist.service.adb.enable=0 stop adbd
init.rc的加载和解析则位于system\core\init目录下的init.c文件中。
编译脚本中的配置主要包含在PRODUCT_PROPERTY_OVERRIDES宏中,实例如下:
PRODUCT_PROPERTY_OVERIDES:=keyguard.no_require_sim=true\
ro.com.android.dateformat=MM-dd-yyyy\
ro.com.android.dataroaming=true\
ro.ril.hsxpa=1\
ro.ril.gprsclass=10
更多的配置信息位于system\core\rootdir\etc下,这些配资文件包括dbus.conf、hosts、init.goldfish.rc、init.goldfish.sh、init.testmenu、mountd.conf和vold.fstab等,其中init.goldfish.rc定义了网络、电池电量和模拟器的一些信息。
配置系统属性的方法为setprop key value,DNS的系统配置:setprop net.eth0.dns1 10.0.2.3
在void.fstab中定义了设备分区信息。设备挂载的方法:dev_mount
其中挂载SD卡的过程:dev_mount sdcard /mnt/sdcard auto /devices/platfrom/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
在Android中,提供了对双SD卡的支持,器挂载过程如下:
dev_mount left_sdcard /sdcard1 auto /devices/platform/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
dev_mount right_sdcard2 /sdcard2 auto /devices/platform/goldfish_mmc.1 /devices/platform/msm_sdcc.3/mmc_host/mmc1
(2)系统属性
配置系统属性是配资Android系统信息的一个方面,部分信息在编译时配置,部分信息在运行时调用接口来配置。属性框架如下图:
事实上,系统属性存储在\tmp\目录下的android-sysprop文件中,在Android中的定义:#define SYSTEM_PROPERTY_PIPE_NAME "/tmp/android-sysprop"
运行期间通过套接字可以获取系统属性。操作系统属性的方法如下:
static int gPropFd=-1;
...
gPropFd=connectToServer(SYSTEM_PROPERTY_PIPE_NAME);
...
pthread_mutex_lock(&gPropertyFdLock);
if(write(gPropFd, sendBuf, sizeof(sendBuf))!=sizeof(sendBuf)){
pthread_mutex_unlock(&gPropertyFdLock);
return -1;
}
系统属性目前可以支持String、Int、Long、Boolean等数据类型,在应用层和框架层,接入系统属性的方法如下:
SystemProperties.set("user.language", 1.getLanguage());
boolean isDebuggableMonkeyBuild=SystemProperties.getBoolean("ro.monkey", false);
编译时的属性配置多分布在init*.rc脚本中,如下是init.goldfish.rc脚本中的一部分实现:
setprop ARGH ARGH
setprop net.eth0.dns1 10.0.2.3
setprop net.gprs.local-ip 10.0.2.15
setprop ro.radio.use-ppp no
setprop ro.build.product generic
setprop ro.product.device generic
6.数据管理
(1)数据备份
在Froyo及以上版本中,Android支持应用数据的备份,在终端恢复出厂设置或更换到一个新的Android终端时,用户可以将数据备份到云存储服务器,方便数据的维护。Android的数据备份功能主要是通过bmgr工具来实现的。为了实现数据备份,必须申请相应应用的数据备份密钥,Android是以应用名作为输入申请密钥,每个密钥仅对应一个包名,相关的网址为:http://code.google.com/intl/zh-CN/android/backup/signup/html。
在默认情况下,Android仅提供对应用的私有文件和配置文件的备份。
在发布程序后,如果是在真实的手机或平板电脑等设备上,可以通过“设置“应用来进行与数据备份相关的操作,如果是基于模拟器进行的,数据备份的主要过程如下:
打开备份功能,在终端执行命令:#adb shell bmgr enable true
将备份请求添加到备份管理器的队列中,在终端执行命令:#adb shell bmgr backup your.pkg.name
执行备份,在终端执行命令:#adb shell bmgr run //执行备份管理器队列中的多有请求
卸载应用,在终端执行命令:#adb uninstall your.pkg.name
然后重新安装应用,应用的数据在联网后即可被初始化为备份数据。
(2)剪切板管理
剪切板管理是在Gingerbread中引入的,在Homeycomb中完善的,它不但可以支持简单的数据类型,而且可以支持复杂的数据类型,如字符串、二进制数据甚至应用的断言等。简单类型的数据直接存储在剪切板上,复杂的数据存储于SQLite中。
在使用剪切板时,对数据的操作是通过剪切对象(Clip Object)进行的,剪切对象有3种:文本、URI和Intent。需要注意,剪切板管理是可以跨应用进行的,而且剪切板只能维护一个剪切对象,新内容会覆盖旧内容。获得剪切板管理的方法如下:
ClipboardManager clipboard=(ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
1)复制数据
为了复制数据,需要利用ClipData,然后通过ClipboardManager的setPrimaryClip()方法即可将数据放到剪切板上。向剪切板上复制数据的方法如下。
文本数据,复制文本数据非常简单,方法如下:lipData clip=ClipData.newPlainText("simple text", "Hello, Workd!");
Uri数据。对于数据库、文件等数据的复制可以通过Uri进行,方法如下:
private static final String CONEACTS="content://com.miaozl.coontacts";
private static final String COPY_PATH="/copy";
Uri copyUri=Uri.parse(CONTACTS+COPY_PATH+"/"+lashName);
ClipData clip=ClipData.newUri(getContentResolver(), "URI", copyUri);
Intent数据。对于二进制数据等,可以通过Intent进行复制,方法如下:
Intent appIntent=new Intent(this, com.miaozl.demo.MyActivity.class);
ClipData clip=ClipData.newIntent("Intent", appIntent);
2)粘贴数据
对数据进行粘贴时,首先需要判断是否可以粘贴。ClipboardManager中用于判断是否有数据可以粘贴的方法:boolean hasPrimaryClip()
下面介绍粘贴文本、URI、Intent的方法。
文本数据。粘贴文本数据的实现方法如下:
ClipData.Item item=clipboard.getPrimaryClip().getItemAt(0);
String pasteData=item.getText();
在某些场景中,有时需要判断剪切板上的数据是否是文本数据,具体的判断方法如下:
clipboard.getPrimaryClipDescription().hasMineType(MIMETYPE_TEXT_PLAIN)
MIME类型主要在ClipDescription中定义,主要类型有MIMETYPE_TEXT_PLAIN、MIMETYPE_TEXT_URILIST和MIMETYPE_TEXT_INTENT等。
Uri数据,Uri数据的粘贴过程略微复杂些,涉及ContentResolver和Cursor的使用,方法如下:
ContextResolver cr=getContentResolver();
ClipData.Item item=clip.getItemAt(0);
Uri pasteUri=item.getUri();
Cursor pasteCursor=cr.query(pasteUri, null, null, null, null);
Intent数据,Intent数据的粘贴比较简单,方法如下:
Intent pasteIntent=clipboard.getPrimaryClip().getItemAt(0).getIntent();
7.设备管理
(1)管理工具
除了SDK提供的Android工具外,在模拟器或实际设备上,Google也提供了丰富的工具供开发者使用,其中Linux工具包括cat、chmod、chown、df、dmesg、gdbserver、ifconfig、insmod、ln、Is、mount、ping等。除此之外,Google还自行开发了一些特殊的工具以满足开发和调试的需要,下面对日常开发需要了解的工具进行简单介绍。
1)驱动调试工具
(1)dmesg显示开机信息
dmesg是一个显示内核缓冲区系统控制信息的工具,在Linux系统中,kernel会将开机时的信息存储在环形缓冲(Ring Buffer)中,其大小为8KB,但是由于某种原因,如Adb尚未建立通信等,开发者无法在开机时获取到设备的开机信息,不利于驱动的调试,dmesg可弥补这一缺陷。dmesg的使用方法如下:#adb shell dmesg
(2)insmod挂载驱动
对于驱动开发,将编译的ko文件挂载到Linux内核中,是非常常见的操作,这一操作可以通过insmod进行,方法是:#adb insmod *.ko
2)网络管理工具
Android提供了多种网络管理工具,如netstat、netcfg、ping、iftop等,因ping和iftop用得比较少,这里就不介绍了。需注意,Android并不支持ifconfig。getprop可以用于查看当前的属性信息,如IP地址、DNS等,起到了与ifconfig类似的作用。
(1)netstat
通过netstat可以获得TCP链接、TCP和UDP监听、进程内存管理等信息,方法是:#adb shell netstat
(2)netcfg
netcfg是Android自定义的用于查看网卡信息的工具,方法是:#adb shell netcfg
3)应用调试工具
目前应用层的调试工具包括am、dumpsys、pm、gdbserver等,通过am可以启动一些应用层活动:通过dumpsys可以观察一些系统信息,帮助开发者做出判断;通过pm可以显示安装包的信息;gdbserver用于GDB远程调试。
(1)am工具
am是一个非常重要的Android应用调试工具,其可以在命令行下启动应用、服务、广播、回归测试、profiling、GDB调试监控(monitoring)等,在一些环境受限的场合非常拥有,其位于system/bin目录下。am的用法如下:
am [subcommand] [options]
启动Activity的方法如下:
am start [-D] [-W]
上述代码中,[-D]表示进入调试模式,[-W]表示等待启动完成
启动服务的方法如下:
am startservice
启动广播的方法如下:
am broadcast
启动回归测试的方法如下:
am instrument [flags]
启动profile的方法如下:
am profile
停止profile的方法如下:
am profile
am进行GDB监控的方法如下:
monitor [--gdb
下面是一些常见的操作场景。
启动Activity的实现:#adb shell am start -n com.google.android.browser/com.google.android.browser.BrowserActivity
拨打电话的实现:#adb shell am start -a android.intent.action.CALL -d tel:10086
打开网页的实现:#adb shell am start -a android.intent.action.VIEW -dhttp://www.163.com
打开地图的实现:#adb shell am start -a android.intent.action.VIEW geo:0,0?q=shanghai
执行JUnit测试的实现:#adb shell am instrument -w com.miaozl.hello/android.test.InstrumentationTestRunner
(2)sumpsys工具
通过dumpsys可以查看到当前运行的广播、Activity栈、服务、进程、安装包、内存信息、闹钟、省电配资、窗体信息、渲染信息、电池信息等方法如下:
#adb shell dumpsys activity //观察Activity栈
#adb shell dumpsys activity broadcasts //观察广播
#adb shell dumpsys activity service //观察服务
#adb shell dumpsys package //观察安装包
#adb shell dumpsys meninfo //观察内存信息
#adb shell dumpsys alarm //观察闹钟
#adb shell dumpsys power //观察省电配置
#adb shell dumpsys window //观察窗体信息
#adb shell dumpsys SurfaceFlinger //观察渲染信息
#adb shell dumpsys batteryinfo //观察电池信息
#adb shell dumpsys /data/anr/traces.txt //查看文件内容
(3)pm工具
利用pm工具可以列出安装包的信息,比如权限,也可以配置应用的安装信息。下面是pm的用法:
pm [list | path | install | uninstall]
pm list package [-f]
pm list permission-groups
pm list permissions [-g] [-f] [-d] [-u] [GROUP]
pm list instrumentation [-f] [TARGET-PACKAGE]
pm list features
pm path PACKAGE
pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH
pm uninstall [-k] PACKAGE
pm enable PACKAGE_OR_COMPONENT
pm disable PACKAGE_OR_COMPONENT
pm setInstallLocation [0/auto] [1/internal] [2/external]
4)属性配置工具
在Android中存在很多的底层属性,一部分属性是不再编译时生成的,一部分属性是在运行时生成的,在进行底层开发或编译环境时,必须熟悉Android的系统属性。常用的系统属性包括ro.build.id、ro.build.display.id、ro.build.version.sdk、ro.build.version.release、ro.runtime.firstboot、net.dns1和gsm.network.type等。
通过getprop获取属性的方法:#adb shell getprop。
通过setprop设置属性的方法:setprop
通过watchprops可以观察属性的变化,如果运行时间系统的属性发生变化,则把变化的值显示出来,方法是:#watchprops
5)getevent/sendevent工具
sendevent是Android系统下的工具,可以模拟多种按键和触屏操作,产生的是原始事件(raw event),原始事件经过处理产生最终的事件。
sendevent的用法是:#adb shell sendevent [device] [type] [code] [value]
sendevent的示例如下:#sendevent /dev/input/event0 1 5 1
sendevent命令中数字格式为十进制,getevent命令中数字格式为十六进制,getevent用来监控按键、拖动、滑动事件。
6)系统管理工具
df常用来查看文件系统的磁盘占用情况,方法是:#adb shell df
mount可以用来挂载文件系统和显示当前文件系统的挂载情况,但是要注意,Android不支持unmount工具。显示当前文件系统的方法是:#mount
Linux支持多种文件系统,如tmpfs、sysfs、ysffs2、vfat等,对于其他常用的文件系统,比如ntfs、ext4、reiserfs、jffs2、romfs、cramfs等。
(2)传感器管理
在框架层,传感器的管理是通过SensorManager进行的,在Java层,和传感器相关的类包括SensorManager、Sensor、SensorListener、SensorEvent和SensorEventListener等,其中SensorListener已被弃用。通过SensorManager可以获取可用的传感器列表、注册传感器事件监听器等,下图是传感器框架的类图:
和其他系统服务一样,通过getSystemService()方法可以获取SensorManager的句柄,方法如下:
mSensorManager=(SensorManager)getSystemService(Context.SENSOR_SERVICE);
通过制定传感器的类型,可以从SensorManager中获取相应类型传感器的列表和默认的传感器,方法如下:
public List
public Sensor getDefaultSensor(int type)
传感器的类型定义在Sensor类中,目前Android支持的传感器类型包括TYPE_ACCELEROMETER、TYPE_MAGNETIC_FIELD、TYPE_ORIENTATION(已弃用)、TYPE_GYROSCOPE、TYPE_LIGHT、TYPE_PROSSURE、TYPE_TEMPERATURE、TYPE_PROXIMITY、TYPE_GRAVITY、TYPE_LINEAR_ACCELERATION、TYPE_ROTATION_VECTOR和TYPE_ALL等。
在Android 4.0中,Google开始支持TYPE_AMBIENT_TEMPEARTRUE(温度传感器)、TYPE_RELATIVE_HUMIDITY(湿度传感器)的传感器。
注册传感器事件监听器的方法如下:
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate, Handler handler)
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate)
注销传感器事件监听器的方法如下:
public void unregisterListener(SensorEventListener listener, Sensor sensor)
private void unregisterListener(Object listener)
public void unregisterListener(SensorEventListener listener)
下面是一个重力传感器的事件监听器的实现:
mSensorManager.registerListener(mGravityListener, mGravitySensor, SensorManager.SENSOR_DELAY_NORMAL);
SensorEventListener mGravityListener=new SensorEventListener(){
public void onSensorChanged(SensorEvent event){
int value=0;
if(event.values[0] < -THRESHOLD){
value+=TILT_LEFT;
}else if(event.values[0] > THRESHOLD){
value+=TILT_RIGHT;
}
if(event.values[1] < -THRESHOLD){
value+=TILT_UP;
}else if(event.values[1] > THRESHOLD){
value+=TILT_DOWN;
}
if(value!=mLastValue){
mLastValue = value;
swith(value){
case TILT_LEFT:
sendCommand(COMMAND_LEFT);
break;
case TILT_RIGHT:
sendCommand(COMMAND_RIGHT);
break;
case TILT_UP:
sendCommand(COMMAND_UP);
break;
case TILT_DOWN:
sendCommand(COMMAND_DOWN);
break;
default:
sendCommand(COMMAND_STOP);
break;
}
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy){
}
};
下面是一个加速度计的事件监听器的实现:
public void onSensorChanged(SensorEvent event){
if(event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
return;
switch(mDisplay.getRotation()){
case Surface.ROTATION_0:
mSensorX=event.values[0];
mSensorY=event.values[1];
break;
case Surface.ROTATION_90:
mSensorX=-event.values[1];
mSensorY=event.values[0];
break;
case Surface.ROTATION_180:
mSensorX=-event.values[0];
mSensorY=-event.values[1];
break;
case Surface.ROTATION_270:
mSensorX=event.values[1];
mSensorY=-event.values[0];
break;
}
mSensorTimeStamp=event.timestamp;
mCpuTimeStamp=System.nanoTime();
}
由以上各示例代码可知,SensorEvent中最常用的参数是values,其中,存储了系统在X、Y、Z三个轴上的角加速度,分别对应于values[0]、values[1]、values[2]。如果所开发的应用某类型的传感器有依赖作用,可通过uses-feature标签对目标环境加以限制。
(3)USB管理
曾经做过嵌入式Linux开发的开发者在进行Android开发时很容易发现,在Android上进行Host/Target调试是如此简单,直接将设备通过USB连接到Linux PC上即可利用adb工具进行日志打印、设备操作等(在Windows上则需安装USB驱动,通常需借助于91助手和豌豆荚等工具)。在Android 3.1中,Google将底层的USB管理框架上移并由开发者开发,这就是android.hardware.usb包的由来。当然,android.hardware.usb包并不仅用于adb调试、还用于插拔以及状态模式的广播。USB框架的类图:
在android.hardware.usb包中,UsbManager提供了最上层的USB管理,可以帮组开发者获取USB设备的状态和与USB设备进行通信,UsbManager通过底层的UsbService进行USB设备操控。获取UsbManager的方法如下:
UsbManager manager=(UsbManager)getSystemService(Context.USB_SERVICE);
通过UsbManager可以获取UsbAccessory、UsbDevice等的列表,并且可以对USB设备进行权限判断和获取临时权限。
UsbAccessory表示和Android设备通信的外围USB设备,通过UsbAccessory,开发者可以获取USB附件的制造商、USB版本(USB 2.0、USB 3.0等)等信息,此时USB附件为Host端。
UsbDevice同样表示和Android设备通信的外围USB设备,但是和UsbAccessory不同,UsbDevice表示的是Android设备为Host端时的外围设备。
如果希望监听到的USB设备的插入,相应的Action为android.hardware.usb.action.USB_DEVICE_ATTACHED。
8.应用发布
如果是作为第三方来开发Android应用,自然就涉及应用发布的问题,在应用发布前,需要了解应用商店和支持的目标环境的情况,前者涉及商业问题,后者涉及用户体验问题,当然后者最后能在开发阶段就考虑。
(1)应用商店概述
自从Apple引入了应用商店以来,这种全新的业务模式启迪了业界正陷入价格战怪圈的人们。这种模式才是移动互联网下的盈利模式,以”硬件“为重心已经被以”服务“为重心代替。
在Android世界,应用商店已经蓬勃发展,Google的应用商店、Motorola移动的智件园、联想的乐园,以及诸多第三方的应用商店相继出现。
以Google的应用商店为例,构成应用商店的要素有用户系统、应用仓库、根据用户所在地区和语言等本地化信息进行的过滤机制,以及交易所需要的计费系统。下面简要介绍用户系统、过滤机制和计费系统,最后介绍保护应用的安全系统。
1)用户系统
为了在应用商店上发布应用,必须进行用户注册。在注册前,应首先注册一个Gmail账户,在注册时需提供所在国家、手机号码和Gmail邮箱等信息,输入完这些基本信息后,就进入CheckOut服务注册了。Google的CheckOut服务目前支持多种信用卡,但是遗憾的是目前尚不支持在中国大陆地区的注册。
除了应用商店客户端外,Google还提供了Web版的应用商店,其网址为http://market.android.com。
需要说明的是,Google的应用商店的注册费为25美元。
2)过滤机制
当一个用户在应用商店上浏览时,其浏览的信息是经过过滤的,这是因为Goolgle等企业的应用商店是面向全球的,因为语言和本地化服务的原因,并不是所有的应用对每个用户都是有价值的。同时由于终端设备的不同,版本的差异也是过滤的考虑因素。
下面是应用商店依据的过滤的要素
(1)supports-screens标签
Android通过supports-screens标签来说明应用支持的屏幕大小。Android目前提供了4种屏幕大小,即小屏、标准屏、大屏、超大屏。
(2)uses-configuration标签
Android通过uses-configuration标签来说明设备配置,Android的设备配置包括键盘、导航、触摸屏等多种设备。其中键盘包括undefined、nokeys、qwerty、twelvekey等;导航包括undefined、nonav、dpad、trackball、wheel等;触摸屏包括undefined、notouch、tylus、finger等。
(3)uses-feature标签
uses-feature标签声明了该应用的目标设备应具有的硬件、软件和OpenGL ES信息。
(4)uses-library标签
部分应用可能要求目标设备包含特定的共享库,这就用到了uses-library标签。
(5)user-permission标签
Android通过uses-permission标签来说明应用具有的用户权限。
严格来说,用户权限并不属于过滤要素范畴,但用户权限会隐式地要求一些硬件信息,如android.permission.READ_SMS就要求目标设备具备通话功能。
(6)uses-sdk标签
Android通过uses-sdk标签来说明应用支持的SDK版本。
由于Android发布周期密集,必须考虑应用和目标设备的软件的兼容性。
除了以上技术方面的因素外,其他因素如应用的发布状态、价格情况、国家信息、运营商信息、原生库(如要求ARM EABI v7)等也是过滤机制必须考虑的。另外需要说明的是,应用商店不会再开发状态设备上显示版本保护应用。
3)计费系统
在购买应用或者开发者获取收入时,显然会涉及分账和缴税服务(目前在中国,监管层尚未将电子商务纳入税收范畴)。由于各个国家情况不同,目前的计费系统十分繁琐、复杂。
目前,Google的计费系统状况不佳,在不久的将来,Google也许会引入eBay的支持系统PayPal解决目前存在的困境。
4)安全系统
对于任何发生的交易,Google必须维护其交易安全,这个问题十分复杂,涉及用户安全、传输安全、计费安全等多种场景和环节。
(2)目标环境的适配
Android的开放性受到诸多厂商的追捧,极大地促进了Android的推广。但开放性也意味着开发的应用将面临更加复杂的环境,不同的CPU、不同的屏幕、不同的功能支持、不同的接入技术、不同的语言环境、不同的设备类型、不同的软件版本、不同的传感器、不同的交互技术等,使需要考虑的底层细节越来越多。
1)功能支持
考虑到OEM厂商推出的Android设备的多样性,对于某些依赖于特定功能的应用而言,可能无法再所有的设备上都正常运行,因此有必要在向目标设备安装Android应用时进行检测。在AndroidManifest.xml中增加了uses-feature标签来处理特定功能的兼容性问题。uses-feature标签具有3个属性:required、glEsVersion和name。其中name定义了硬件设备项;glEsVersion定义了是否支持openGL;required定义了对特定功能的需求程序;required属性在Eclair中引入。uses-feature标签在Dount中引入。如果目标环境为Cupcake等更老的环境,那么uses-feature标签将被忽略。
对于OpenGL ES加速,如果要求支持OpenGL ES 2.0,glEsVersion的属性值为0x00020000;如果要求支持OpenGL ES 2.1,glEsVersion的属性值为0x00020001。如果不声明glEsVersion属性,则系统假定应用仅要求支持OpenGL ES 1.0,参考配置如下:
摄像头配置:
摄像头自动对焦功能配置:
支持动态墙纸功能配置:
支持NFC功能配置:
支持SIP功能配置:
支持WiFi功能配置:
在默认情况下,在设置uses-feature标签时意味着该应用要求硬件平台必须具备某些特定功能。如果设置其required属性为false,则意味着该应用倾向于要求具备某些特定功能,即使不具备相应的特定功能,该应用同样可以运行,示例如下:
需要注意的是,为了设计方便,声明的功能可以包括硬件和软件两个方面,下面是uses-feature标签的详细说明:
另外,在开发者对特定权限进行声明时,会隐式声明uses-feature标签,例如声明android.permission.ACCESS_WIFI_STATE权限,意味着目标设备应支持android.hardware.wifi功能。
2)语言环境
全球有70亿左右的人口,得到承认的语言有4200种左右,联合国通用语言就包括了英语、汉语、法语、西班牙语、阿拉伯语等5种,不同的语言在字符集中的位置不同,表达同一个含义的用语长度更是千差万别。软件的本地化在开发中是个永恒的话题。
(1)字符集
由于历史的原因,目前最常用的字符集包括ASCII、UTF-8、UTF-16,UTF-8为了保持和ASCII兼容,在支持ASCII字符集中的字符时执行8位编码,在支持中文等字符时执行24位编码,而UTF-16则对所有字符执行16位编码。
判断当前语言环境的方法如下:
Locale locale=Locale.getDefault();
String language=locale.getLanguage();
String country=locale.getCountry();
将UTF-16的String类型的字符串转化为byte[]类型的字节数组的方法如下:
byte[] textBytes=text.getBytes(Charsets.UTF_8);
将byte[]类型的字节数组转化为UTF-16的String类型的字符串的方法如下:
String name=new String(textBytes, Charsets.UTF_8);
(2)排列方向
Android在Froyo版本前尚不支持阿拉伯等自右向左排序的语言,但是,基于Froyo版本,在root权限下,可以通过添加资源包的方式支持阿拉伯语。
(3)用语长度
3)屏幕布局
4)软件版本
5)接入技术
6)传感器
7)交互技术
(3)发布应用
发布一个应用大致需要以下几个步骤:
设置版本信息
设置应用图标和应用名
关闭调试信息
添加各种许可文件
为应用程序进行数字证书签名
进行发布测试
发布应用程序
1)设置版本信息
考虑到Android的不同版本间的兼容性和对应用程序本身的版本管理,Android需要在AndroidManifest.xml中对支持的版本信息进行说明。发布的应用必须设置版本信息。
Android平台的版本说明在AndroidManifest.xml的uses-sdk标签中进行,其包括3个属性,即android:minSdkVersion、android:targetSdkVersion和android:maxSdkVersion。其中,android:minSdkVersion属性定义了应用程序可以运行的最低Android版本,该属性是必须定义的;android:targetSdkVersion属性定义了应用程序运行的最佳Andorid版本;android:maxSdkVersion属性定义了应用程序能够运行的最高Android版本。
应用程序的版本说明是通过android:versionCode和android:versionName两个属性定义的。其中android:versionCode属性定义了应用程序的版本号,主要用于应用升级(注意,其属性值为整数型,笔者认为该属性值是浮点型会更有利于版本的维护);android:versionName属性定义了应用程序的版本名,用于给出应用程序的更详细信息,如注版本(major)、
设置版本信息的方法如下:
android:versionCode="1"> 2)设置应用图标和应用名 作为应用程序的入口,应用图标和应用名应当受到重视,这是一个应用的“脸面”。应用图标的制作较复杂,Android定义了一套完整的规范,设置应用图标的方法如下: 3)关闭调试信息 ‘ 考虑性能和数据安全方面的因素,要在发布调试信息前将其关闭,涉及的主要包括: 设置application标签的android:debuggable属性为false。 屏蔽Log方法的调用。 移除和调试相关的其他信息。 4)添加各种许可文件 为了保护开发者的商业信息,开发者可以根据自己的需要为开发的应用添加各种许可文件,比如Apache许可、Android Market许可等。 5)为应用程序进行数字证书签名 Android系统要求必须对所有的应用进行数字证书签名,否则无法将其安装到实际设备上,由于Android进行数字证书签名的目的并不在于应用程序的可信性,而是在于识别应用程序的作者和维护不同应用间的关系,因此,采用的数字证书不必是经过CA认证的,自签名的证书即可。 注意:在进行应用程序安装时,系统会检测应用程序的数字签名到期时间,过期了的数字签名也是无效的。 6)进行发布测试 在进行数字证书签名后,即可进行发布前的最后一项工作--测试。关于Android测试的方法,比如压力测试、JUnit测试等。 7)发布应用程序 准备工作完成后即可发布应用程序。考虑到软件商店的差异。而且发布应用程序并非技术性的工作。