• 怎样在Android实现桌面清理内存简单Widget小控件


    怎样在Android实现桌面清理内存简单Widget小控件


    我们常常会看到类似于360、金山手机卫士一类的软件会带一个widget小控件,显示在桌面上,上面会显示现有内存大小,然后会带一个按键功能来一键清理内存,杀死后台进程的功能,那么这个功能是怎样实现的呢,我们今天也来尝试做一个类似的功能的小控件。

    效果图:



    一、UI部分的编写:

    參照Google的文档,首先在建立一个类继承AppWidgetProvider

    import android.appwidget.AppWidgetProvider;
    
    public class MyWidget extends AppWidgetProvider {
    	
    }

    然后在清单文件里申明它。我们必须注意到,AppWidgetProvider实际上是BroadcastReceiver,所以要注冊成一个receiver,然后另一些其它的东西须要注意:

     <receiver android:name="com.alexchen.widget.MyWidget" >
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
    
                <meta-data
                    android:name="android.appwidget.provider"
                    android:resource="@xml/example_appwidget_info" />
            </receiver>
    android.appwidget.action.APPWIDGET_UPDATE 表明这个receiver可以接受一个APPWIDGET_UPDATE的广播,并且在这里,仅仅能增加这一个action。


    android.appwidget.provider 表明数据类型时widget提供者提供的数据,example_appwidget_info表明这个widget的參数配置文件名称和位置


    那么接下来就须要在res文件夹下建立一个xml文件夹。而且在当中建立一个example_appwidget_info.xml的配置文件,Google的文档中给出了演示样例有非常多參数。实际上关键的參数仅仅有以下的4个:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="94dp"
        android:minHeight="72dp"
        android:updatePeriodMillis="86400000"
        android:initialLayout="@layout/example_appwidget">
    </appwidget-provider>

    当中,minWidthminHeight代表这个widget控件所占领的最小空间,这个空间一般来讲不须要太大,由于太大的话,一个屏幕可能都没办法放下。Google的官方文档的说法是大于4x4的就可能无法显示。

    updatePeriodMillis代表数据更新的时间。这里86400000毫秒实际上是24小时,可能最開始看到这个參数会想我是否能将其设的非常小,每一秒刷新非常多次?,实际上对于updatePeriodMillis这个參数而言,即算你设的再小也没用,Google设定widget控件这个參数控制的最短update时间为30分钟,就算将其设置在30分钟以内也会以30分钟的频率来更新数据。

    initialLayout參数代表的是本widget空间的布局文件。



    那么下一步就是定义出一个相应的布局文件。

    我们能够简单的在layout文件夹下建立一个布局文件example_appwidget.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="200dp"
        android:layout_height="80dp"
        android:background="@android:color/white"
        android:gravity="center_vertical"
        android:orientation="vertical" >
    
        <TextView
            android:id="@+id/tv_widget"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="widget控件測试"
            android:textColor="@android:color/black"
            android:textSize="15sp" />
    
        <Button
            android:id="@+id/btn_clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="清理内存"
            android:textColor="#ff000000" />
    
    </LinearLayout>


    在Google的文档中有指出,并不是全部的布局组件都能够在上面的这个布局中生效,有效的组件或布局为:


    至此。一个简单的widget控件就写好了,我们能够在模拟器上将其拖到桌面上看一看效果:



    二、功能逻辑部分的实现

    大部分的Widget小控件都会须要在特定情况下更新上面显示的数据。那么这个是怎样实现的呢,我们经过上面的代码不难发现实际上这个widget控件并没有一个Activity,所以说这个控件的显示实际上不是本应用来实现的。它实际上是桌面这个应用来显示的,所以我们也不可能直接去更新它上面的数据。


    回过头去看看上面我们写的那个receiver。实际上没有实现不论什么方法。实际上AppWidgetProvider里面有几个比較重要的方法:onReceive、onUpdate、onDisabled、onEnabled

    当中onReceive方法跟大多数广播接收者的onReceive方法一样,可是在这里。onReceive方法的调用并非我们能够决定的,它依赖于显示该widget控件的Host组件。在这里也就是Android桌面应用。所以我们会发如今不同的手机上。将widget控件拖到桌面上显示的时候onReceive可能调用的次数和先后顺序可能全然不一样。这依赖于Host组件是怎样实现的。

    所以在这里onReceive方法对于我们刷新widget数据基本没有什么帮助。


    onUpdate方法则是由上面所说的updatePeriodMillis參数来控制的,经过上面的分析。我们都知道了,它的最小周期为30分钟。所以我们一般将这个參数设为0就可以。那么在这种方法里,我们往往会在当中放置一些启动更新数据服务的功能。由于假设后台的更新数据的Service被意外停止了,那么每30分钟还会被又一次启用。不至于一直启动不了了:

    	@Override
    	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    			int[] appWidgetIds) {
    		super.onUpdate(context, appWidgetManager, appWidgetIds);
    		// System.out.println("onUpdate");
    		//每隔一段时间又一次启动服务,防止服务中间被终止了之后没法重新启动
    		Intent intent = new Intent(context, UpdateWidgetService.class);
    		context.startService(intent);
    	}

    以下是比較重要的两个方法了:onDisabled和onEnabled

    我们知道。widget小控件是可以拖动多个到桌面上的,而onEnabled方法会在第一个widget控件拖到桌面上的时候调用一次,onDisabled会在最后一个widget控件从桌面被删除时调用一次,那么我们须要做的就是在onEnabled这种方法中启用一个刷新widget数据的服务,在onDisabled方法中使用stopService方法来停止这个服务。

    @Override
    	public void onDisabled(Context context) {
    		super.onDisabled(context);
    		System.out.println("onDisabled");
    		//停止数据刷新服务
    		Intent intent = new Intent(context, UpdateWidgetService.class);
    		context.stopService(intent);
    	}
    
    	@Override
    	public void onEnabled(Context context) {
    		super.onEnabled(context);
    		System.out.println("onEnabled");
    		//开启数据刷新服务
    		Intent intent = new Intent(context, UpdateWidgetService.class);
    		context.startService(intent);
    	}

    三、刷新数据的服务

    那么以下的任务就仅仅剩下UpdateWidgetService这个刷新数据的服务(Service)怎样实现的问题了。

    我们在这里的想法非常easy。比方说每隔三秒钟来刷新一下widget中的数据。Android中定时运行任务的方法有非常多。我们这里使用TimerTimerTask来实现,之后我们须要关心的就是详细怎样实现刷新widget中的数据。毕竟这些数据是在桌面应用中显示的。

    而且我们须要用到一个API--AppWidgetManager,它有一个实例方法AppWidgetManager.updateAppWidget(ComponentName provider, RemoteViews views)来实现更新widget数据。我们都知道,假设须要调用另外应用的方法。须要使用远程调用的方法来实现,在这里起到在我们的应用和桌面应用之间的桥梁作用的就是这第二个參数:RemoteViews views。它会将我们设置的数据传送到桌面应用来刷新widget上的数据,我们须要经过以下几步:

    1、定义一个RemoteViews的实例:

    RemoteViews views = new RemoteViews(getPackageName(),R.layout.process_widget);

    2、设置views的内容。也就是刷新当中的数据,这里的方法名会比較奇怪,RemoteViews.setTextViewText(int viewId, CharSequence text)

    当中viewId是在我们前面定义的widget布局文件里的子组件的id,也就是我们要刷新内容的对象,这里就是R.id.tv_test。第二个參数是我们要更新的内容

    3、定义好第一个參数ComponentName provider之后,就能够调用AppWidgetManager.updateAppWidget(ComponentName provider, RemoteViews views)来实现更新数据

    if (timer == null && task == null) {
    	//AppWidgetManager对象,用于更新widget的数据
    	awm = AppWidgetManager.getInstance(this);
    	timer = new Timer();
    	task = new TimerTask() {
    		@Override
    		public void run() {
    			ComponentName provider = new ComponentName(UpdateWidgetService.this, MyWidget.class);
    			//远程view对象,用于在本应用和桌面应用中起传递数据的桥梁作用
    			RemoteViews views = new RemoteViews(getPackageName(),R.layout.example_appwidget);
    			views.setTextViewText(R.id.tv_widget, "想刷新的数据的内容");
    			awm.updateAppWidget(provider, views);
    			System.out.println("====刷新了widget====");
    		}
    	//设置循环时间
    	timer.schedule(task, 0, 3000);
    }


    四、定时刷新可用内存和一键清理内存功能实现

    要实现这个功能,我们须要再上面定时刷新数据服务中将定时刷新的内容改为当前内存所剩余的量,我们这里写一个工具类方法来实现返回内存剩余量;

    另外我们还须要在widget控件的布局文件里加入一个button,并在更新widget数据的服务中,设置这个button的点击事件,可是这里也不像曾经的点击事件,相同要应用到RemoteView对象,在这个点击事件中须要发送一个广播,Action为自己定义的。我们这里设为:"com.alexchen.mobilesafeexercise.killall",之后,我们须要再写一个广播接收者,来接收这个广播,在onReceive方法中运行杀死后台进程的操作。这里也不能直接使用Intent,因为我们这个意图的Action不是由我们自己运行而是由其它应用程序(桌面应用)运行的,所以须要用到PendingIntent

    刷新widget数据的服务代码:

    package com.alexchen.widget.service;
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    import android.app.PendingIntent;
    import android.app.Service;
    import android.appwidget.AppWidgetManager;
    import android.content.BroadcastReceiver;
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.os.IBinder;
    import android.text.format.Formatter;
    import android.widget.RemoteViews;
    
    import com.alexchen.widget.MyWidget;
    import com.alexchen.widget.R;
    import com.alexchen.widget.utils.SystemInfoUtils;
    
    public class UpdateWidgetService extends Service {
    	private Timer timer;
    	private TimerTask task;
    	private AppWidgetManager awm;
    
    	@Override
    	public IBinder onBind(Intent intent) {
    		return null;
    	}
    
    	@Override
    	public void onCreate() {
    		super.onCreate();
    		startTimer();
    	}
    
    	private void startTimer() {
    		if (timer == null && task == null) {
    			awm = AppWidgetManager.getInstance(this);
    			timer = new Timer();
    			task = new TimerTask() {
    
    				@Override
    				public void run() {
    					ComponentName provider = new ComponentName(
    							UpdateWidgetService.this, MyWidget.class);
    					RemoteViews views = new RemoteViews(getPackageName(),
    							R.layout.example_appwidget);
    					views.setTextViewText(R.id.tv_widget, "dd");
    					views.setTextViewText(R.id.tv_widget,
    							"可用内存:"+ Formatter.formatFileSize(getApplicationContext(),
    								SystemInfoUtils.getAvailableMem(getApplicationContext())));
    					// 自己定义一个广播,杀死后台进程的事件
    					Intent intent = new Intent();
    					intent.setAction("com.alexchen.mobilesafeexercise.killall");
    
    					// 描写叙述一个动作,这个动作是由另外一个应用程序运行的
    					PendingIntent pendingIntent = PendingIntent.getBroadcast(
    							getApplicationContext(), 0, intent,
    							PendingIntent.FLAG_UPDATE_CURRENT);
    					views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
    					awm.updateAppWidget(provider, views);
    					System.out.println("====刷新了widget====");
    				}
    			};
    			timer.schedule(task, 0, 3000);
    		}
    	}
    	@Override
    	public void onDestroy() {
    		super.onDestroy();
    		stopTimer();
    		unregisterReceiver(offReceiver);
    		unregisterReceiver(onReceiver);
    	}
    
    	private void stopTimer() {
    		if (timer != null && task != null) {
    			timer.cancel();
    			task.cancel();
    			task = null;
    			timer = null;
    		}
    	}
    }
    

    按键清理内存的广播接收者:

    package com.alexchen.widget.receiver;
    
    import java.util.List;
    
    import android.app.ActivityManager;
    import android.app.ActivityManager.RunningAppProcessInfo;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    
    public class KillAllReceiver extends BroadcastReceiver {
    
    	@Override
    	public void onReceive(Context context, Intent intent) {
    		System.out.println("自己定义的广播消息接收到了...開始清理内存...");
    		ActivityManager am = (ActivityManager) context
    				.getSystemService(Context.ACTIVITY_SERVICE);
    		List<RunningAppProcessInfo> runningAppProcesses = am
    				.getRunningAppProcesses();
    		for (RunningAppProcessInfo info : runningAppProcesses) {
    			am.killBackgroundProcesses(info.processName);
    		}
    	}
    }
    

    获取可用内存的工具类方法:

    /**
    	 * 获取手机可用的剩余内存
    	 * 
    	 * @param context
    	 *            上下文
    	 * @return
    	 */
    	public static long getAvailableMem(Context context) {
    		ActivityManager am = (ActivityManager) context
    				.getSystemService(Context.ACTIVITY_SERVICE);
    		MemoryInfo outInfo = new MemoryInfo();
    		am.getMemoryInfo(outInfo);
    		return outInfo.availMem;
    	}


  • 相关阅读:
    【HDU3032】Nim or not Nim?(博弈论)
    【BZOJ1022】小约翰的游戏(博弈论)
    【HDU1848】Fibonacci again and again(博弈论)
    【POJ1704】Georgia and Bob(博弈论)
    【BZOJ1299】巧克力棒(博弈论,线性基)
    【Luogu2197】NIM游戏(博弈论)
    【洛谷2252&HDU1527】取石子游戏(博弈论)
    【HDU1846】Brave Game(博弈论)
    【CF888G】Xor-MST(最小生成树,Trie树)
    长链剖分学习笔记
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5129287.html
Copyright © 2020-2023  润新知