实现金山卫士的小控件(Widget)

0.思路:反编译金山的apk,在清单文件中根据android.appwidget.action.APPWIDGET_UPDATE找到入口,进而一步步向下

1. 自定义类继承自AppWidgetProvider

public class ProcWidget extends AppWidgetProvider {

}

2. 在AndroidManifest.xml文件中配置,AppWidgetProvider类继承自BroadcastReceiver,因此配置receiver节点

<receiver android:name="com.cbooy.widget.ProcWidget" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/process_widget_provider" />
</receiver>

3.在res目录下新建xml目录,新建文件process_widget_provider.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/process_widget"
    android:minHeight="72.0dip"
    android:minWidth="294.0dip"
   <!--设置更新时间为0,Google会自行判断,此处时间最小为30分钟,因此设置时间小于30分钟会按照30分钟处理-->
android:updatePeriodMillis="0" />

4. 在layout下新建布局文件 process_widget.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/widget_bg_portrait"
    android:gravity="center_vertical" >

    <LinearLayout
        android:layout_width="0.0dip"
        android:layout_height="fill_parent"
        android:layout_marginLeft="5.0dip"
        android:layout_weight="1.0"
        android:background="@drawable/widget_bg_portrait_child"
        android:gravity="center_vertical"
        android:orientation="vertical"
        android:paddingBottom="3.0dip"
        android:paddingTop="3.0dip" >

        <TextView
            android:id="@+id/process_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10.0dip"
            android:textAppearance="@style/widget_text"
            android:text="正在运行的软件:17" />

        <ImageView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="1.0dip"
            android:layout_marginTop="1.0dip"
            android:background="@drawable/widget_bg_portrait_child_divider" />

        <TextView
            android:id="@+id/process_memory"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10.0dip"
            android:textAppearance="@style/widget_text"
            android:text="可用内存:430.22mb" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="vertical" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical" >

            <ImageView
                android:layout_width="20.0dip"
                android:layout_height="20.0dip"
                android:src="@drawable/main_icon" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/app_name"
                android:textColor="#ffffffff" />
        </LinearLayout>

        <Button
            android:id="@+id/btn_clear"
            android:layout_width="90.0dip"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginTop="5.0dip"
            android:background="@drawable/function_greenbutton_selector"
            android:text="一键清理"
            android:textColor="@drawable/function_greenbutton_textcolor_selector" />
    </LinearLayout>

</LinearLayout>

5. 相关资源文件

  style

    <style name="widget_text">
        <item name="android:textSize">16.0dip</item>
        <item name="android:textColor">#ff000000</item>
    </style>

  drawable下资源function_greenbutton_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@drawable/function_greenbutton_pressed" />
    <item android:state_focused="true" android:drawable="@drawable/function_greenbutton_pressed" />
    <item android:drawable="@drawable/function_greenbutton_normal" />
</selector>

  drawable下资源function_greenbutton_textcolor_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:color="#80ffffff" />
    <item android:state_focused="true" android:color="#80ffffff" />
    <item android:state_selected="true" android:color="#80ffffff" />
    <item android:color="#ffffffff" />
</selector>

  drawable下的图片资源

                                                              

6. 实现修改widget页面的数据,由于widget是显示的桌面程序中,因此与自己的程序属于两个不同的进程,因此修改数据是跨进程的通信

  补全ProcWidget类

public class ProcWidget extends AppWidgetProvider {

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);

        protectServiceRunning(context);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);

        protectServiceRunning(context);
    }

    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);

        startRefreshWidgetUIService(context);
    }

    @Override
    public void onDisabled(Context context) {
        super.onDisabled(context);

        stopRefreshWidgetUIService(context);
    }

    /**
     * 若用户在后台清理掉了此进程,在widget有任何操作时都会调用此方法,因此可以保护服务开启
     * 
     * @param context
     */
    private void protectServiceRunning(Context context) {
        boolean isRefreshWidgetServiceRunning = ServicesUtil.isServiceRun(
                context, RefreshWidgetUIService.class.getName());
        if (!isRefreshWidgetServiceRunning) {
            startRefreshWidgetUIService(context);
        }
    }

    /**
     * 开启widget刷新UI服务
     * 
     * @param context
     */
    private void startRefreshWidgetUIService(Context context) {
        Intent intent = new Intent(context, RefreshWidgetUIService.class);

        context.startService(intent);
    }

    /**
     * 停止widget刷新服务
     * 
     * @param context
     */
    private void stopRefreshWidgetUIService(Context context) {
        Intent intent = new Intent(context, RefreshWidgetUIService.class);

        context.stopService(intent);
    }
}

新建服务,来完成数据的刷新功能,要在清单文件中注册

public class RefreshWidgetUIService extends Service {
    
    private Timer timer;
    
    private TimerTask timerTask;
    
    private AppWidgetManager widgetManager;
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        
        schedultTask();
    }
    
    /**
     * 本地 调度任务,立即执行,3秒钟执行一次
     */
    private void schedultTask() {
        timer = new Timer();
        
        timerTask = new TimerTask() {
            @Override
            public void run() {
                refreshWidgetData();
            }
        };
        
        timer.schedule(timerTask, 0, 3 * 1000);
    }
    
    /**
     * 刷新数据
     */
    private void refreshWidgetData() {
        widgetManager = AppWidgetManager.getInstance(RefreshWidgetUIService.this);
        
        // 设置更新的组件
        ComponentName componentName = new ComponentName(RefreshWidgetUIService.this,ProcWidget.class);
        
        // 跨进程通信使用的View
        RemoteViews views = new RemoteViews(getPackageName(),R.layout.process_widget);
        
        // 进程管理的工具类,提供getRunningProcSize()获取运行进程的个数,getAvaliMem()获取可用内存大小
        ProcessManage pm = ProcessManage.buildWithContext(RefreshWidgetUIService.this);
        
        views.setTextViewText(R.id.process_count, new StringBuilder("正在运行的进程:").append(pm.getRunningProcSize()));
        
        views.setTextViewText(R.id.process_memory, new StringBuilder("可用内存:").append(pm.getAvaliMem()));
        
        Intent intent = new Intent();
        
        intent.setAction("com.cbooy.mmap.clean_procs");
        
        // 描述一个动作,此动作由另外的进程来执行,此时是桌面发出来一个广播,来接受响应事件
        // FLAG_UPDATE_CURRENT 第二次消息会把第一次的消息给覆盖掉
        PendingIntent pendingIntent = PendingIntent.getBroadcast(RefreshWidgetUIService.this, 0, intent , PendingIntent.FLAG_UPDATE_CURRENT);
        
        views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent );
        
        widgetManager.updateAppWidget(componentName, views);
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        
        timer.cancel();
        
        timerTask.cancel();
        
        timer = null;
        
        timerTask = null;
    }
}

7.注意事项

App必须安装在手机内存中才可以展示,安装在sdcard中则无法展示,若配有android:installLocation="preferExternal" 则修改,或移动回内存中

8.效果图

注:以上代码有一些细节可能会有出入,都是一些资源文件类的名字,修改很简单,还有一些清单文件的注册请注意

原文地址:https://www.cnblogs.com/cbooy/p/4753294.html