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.效果图
注:以上代码有一些细节可能会有出入,都是一些资源文件类的名字,修改很简单,还有一些清单文件的注册请注意