1.背景
转到Android组多日,总有些空虚感,因为之前在WinCE都是做的很底层的驱动,像显示驱动、USB Device驱动、USB Host EHCI、OHCI、2D加速驱动,显示驱动还使用到了NEON机器码!没错是机器码,因为VS2005的ARMASM编译器不支持Cortex-A8才有的NEON指令,所以只好写机器码代替。而在Android这边因为刚刚入手,Framework都不是很熟,只能先做一些简单的任务,这周主要就是完成一个关机的Appwidget。
Appwidget直译是窗口小部件,类似Win7系统里面桌面中的小闹钟、日历等,在Android中可以自由拖放。下面是一个闹钟的Appwidget。
好了,废话不多说,我们先分析怎么实现这些功能。
2.分析
2.1Android关机流程
Android关机流程的介绍网上很多,现在摘抄一段如下:
关机动作从按键触发中断,linux kernel层给android framework层返回按键事件进入 framework层,再从 framework层到kernel层执行kernel层关机任务。 长按键对应的handler代码: frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java Runnable mPowerLongPress; private final Runnable mPowerLongPress = new Runnable() { public void run() { if (!mPowerKeyHandled) { mPowerKeyHandled = true; performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); showGlobalActionsDialog(); } } }; mPowerLongPress 启动关机对话框 (frameworks/policies/base/phone/com/android/internal/policy/impl/GlobalActions.java) 如果我们选择Power OFF’,会调用 ShutdownThread.shutdown. 启动关机线程执行关机动作。 frameworks/base/core/java/com/android/internal/app/ShutdownThread.java 真正关机 流程: (1)广播全局事件, ACTION_SHUTDOWN Intent (2)shutdown ActivityManager 服务 (3) 停止蓝牙服务 (4) 停止 电话服务 (radio phone service) (5)停止mount 服务 (6) 调用 Power.shutdown() 进入native 层 frameworks/base/core/java/android/os/Power.java power的native实现代码: frameworks/base/core/jni/android_os_Power.cpp static void android_os_Power_shutdown(JNIEnv *env, jobject clazz) { sync(); #ifdef HAVE_ANDROID_OS reboot(RB_POWER_OFF); #endif } sync, reboot 为linux系统调用,进入linux内核关机流程。 完毕。
仔细按照上面说的流程跟下去,确实是这样的,只不过根据产品的不同,会有一定的修改,例如产品是平板电脑,就会比手机少很多废话对话框,如果是智能电视,则又会有不同,对于这个任务来说,比较重要的ShutdownThread.java这个文件,这个文件启动了关机的对话框,关机对话框效果如图:
PhoneWindowManager中调用ShutdownThread的代码如下:
1 mPowerKeyHandled = true; 2 performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); 3 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); 4 ShutdownThread.shutdown(mContext, true);
传入第一个参数不解释,第二个参数是是否显示对话框,true就是显示。
2.2应用中关机的方法
凭借网络这个好老师,我找到了好几种实现关机的方法,一是通过向控制台写shutdown命令完成,Android是建立在Linux基础上的,所以这种方法需要Root。
二是启动ShutdownThread中的对话框,PhoneWindowManager直接通过一句ShutdownThread.shutdown(mContext, true);就启动了,后面我也试过这种方法,确实可以启动,但是点击确定却一直关不了机。所以靠谱的方法还是通过Intent启动。方法如下:
1 Intent shutdown = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); 2 shutdown.putExtra(Intent.EXTRA_KEY_CONFIRM, true); 3 shutdown.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); 4 startActivity(shutdown);
这上面的一些Flag值AndroidSDK中并没有公开,所以工程要放在Android源码中才能编译通过,除此之外还需要设置系统权限,需要通过修改AndroidManfast.xml文件实现。
2.3Appwidget的框架
个人感觉AppWidget像是嵌入launcher中的View,和AppWidget本身Activity的不是运行在同一个进程中,所以控制Appwidget都需要用RemoteViews控制。
桌面上有多个不同的AppWidget,如何得知哪一个被激活呢,这主要通过广播完成,广播发出的是在AndroidManfast.xml中定义好的android:name,接收时需要在OnReceive()方法中进行判断。
AndroidMainfast.xml摘录如下:
<receiver android:name="AppWidget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"> </action> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget01" /> <intent-filter> <action android:name="com.android.shutdownapp"></action> </intent-filter> </receiver>
Appwidget的处理片段如下:
1 @Override 2 public void onReceive(Context context, Intent intent) 3 { 4 if (intent.getAction().equals(broadCastString)) 5 { 6 RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.appwidgetlayout); 7 remoteViews.setTextViewText(R.id.btnSend, "shutdown"); 8 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 9 ComponentName componentName = new ComponentName(context,AppWidget.class); 10 appWidgetManager.updateAppWidget(componentName, remoteViews); 11 } 12 super.onReceive(context, intent); 13 }
3.实现
3.1建立Appwidget工程
同建立普通Activity工程一样,不过要手动添加一些XML文件,这里有一篇写的很不错的博文,推荐一下,可以按照他介绍的方法一步步建立工程:
http://www.cnblogs.com/qianlifeng/archive/2011/03/26/1996407.html
3.2把Eclipse工程加入到Android工程中
建立了Demo之后,需要将其添加到Android工程目录中去编译,因为我们调用了一些SDK中并没有公开的方法,建议拷贝到的目录是\android4.0.1\development\apps下,然后删除一些Eclipse的文件,只留下\res、\src、\AndroidManifast.xml这几个文件和目录。
接着我们需要一个Android.mk文件,简单,到同级的其他工程中拷贝一个就是,但需要修改LOCAL_PACKAGE_NAME := 为你工程的名字,另外还要有LOCAL_CERTIFICATE := platform这一行存在,我的MK文件如下:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := ShutdownAppWidget LOCAL_CERTIFICATE := platform include $(BUILD_PACKAGE)
3.3添加工程的系统属性
因为调用到了系统对话框,所以整个工程需要系统属性,这通过修改AndroidManifastwen
文件实现,修改后的文件内容如下(注意其中绿色的内容):
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.shutdownapp" android:sharedUserId="android.uid.system"> <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW "/> <uses-permission android:name="android.permission.BIND_APPWIDGET" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".ShutdownApp" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name="AppWidget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget01" /> <intent-filter> <action android:name="com.android.shutdownapp"></action> </intent-filter> </receiver> </application> </manifest>
3.4把Appwidget添加到launcher中
添加到Launcher中可以实现烧写镜像后AppWidget就存在于界面上,不需要人手工去拖,对应的配置文件在以下的位置:
点击(此处)折叠或打开
这个文件的摘录如下:
<favorites xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"> <!-- Far-left screen [0] --> <!-- Left screen [1] --> <appwidget launcher:packageName="com.android.settings" launcher:className="com.android.settings.widget.SettingsAppWidgetProvider" launcher:screen="1" launcher:x="0" launcher:y="3" launcher:spanX="4" launcher:spanY="1" /> <!-- Middle screen [2] --> <appwidget launcher:packageName="com.android.deskclock" launcher:className="com.android.alarmclock.AnalogAppWidgetProvider" launcher:screen="2" launcher:x="1" launcher:y="0" launcher:spanX="2" launcher:spanY="2" />
文件中已经有配置的例子,可以按照其中的例子自己去配,需要主要的是其中的坐标不是像素,而是一些定义好的点,在点击屏幕安放AppWidget时就可以看见一些点,这些就是对应的坐标位置。
4.效果
AppWidget显示如下:
按下后效果如下:
还不太完善,显示了“Android系统”的背景还不知如何去掉,另外界面也不太友好。
5.总结
和同事沟通确实很重要,之前一直不知道怎样在Launcher中配置Appwidget,结果问了一个阅读过Launcher的同事很快就找到方法了。
另外发现自己有点太过于依赖网络,这点确实需要改正一下,还是自己思考的进步快。