代码改变世界

(转)Android关机AppWidget的实现

2012-08-01 16:21  ...平..淡...  阅读(501)  评论(0编辑  收藏  举报

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就存在于界面上,不需要人手工去拖,对应的配置文件在以下的位置:

点击(此处)折叠或打开

  1. /android/packages/apps/Launcher2/res/xml/default_workspace.xml

    这个文件的摘录如下:

 

<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的同事很快就找到方法了。
   另外发现自己有点太过于依赖网络,这点确实需要改正一下,还是自己思考的进步快。

转自:http://blog.chinaunix.net/uid-26926660-id-3201895.html