小部件在 Android 5.1 中运行不佳,但在 4.1、4.4 等中运行良好

Widget not working well in Android 5.1 but perfect in 4.1, 4.4 etc

几年前我开发了一个小部件。到目前为止,该小部件可以完美运行。它在 Android 5.1.1 设备中运行不正常,但在 Android 4.4 及更低版本中运行完美。该小部件显示操作系统给出的三个数值。这些值是正确获得的,因为我可以在测试应用程序中的这个小部件之外显示它们,并且它们在那个测试非小部件应用程序中得到更新。

在 android 5.1.1 中,小部件菜单中没有显示任何图标,它显示的是默认 android 图标。另外,小部件没有更新!!!我不知道出了什么问题。我需要一个必须在 4.1、4.4 和 5.1 设备上运行的小部件。

Android 必须实施的 5.1.1 小部件发生了一些变化?我在文档中找不到任何不同之处

这是我的小部件:

清单:

<receiver android:name="com.myapp.CPUWidgetProvider" android:label="@string/app_name" android:icon="@drawable/icon128">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <intent-filter>
                <action android:name="AUTO_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_provider" />
        </receiver>

        <service android:name="com.myapp.UpdateService"/>

我的widget_provider.xml文件:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:minWidth="40dp"
   android:minHeight="40dp"
   android:initialLayout="@layout/widget_layout"
   android:updatePeriodMillis="0"
/>

我的 widget_layout.xml 文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fondo"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/widget_background"    
    android:paddingLeft="1dp"
    android:paddingRight="1dp"
    android:paddingTop="10dp"
    android:paddingBottom="10dp">

    <TextView
    android:id="@+id/freq"  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="@string/loading"          
    android:textSize="12sp"
    android:textColor="#EE08B608"
    android:layout_weight="1"/>     

    <TextView
    android:id="@+id/usage"  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="..."
    android:textSize="12sp"
    android:textColor="#EE08B608"
    android:layout_weight="1"/>             

    <TextView
    android:id="@+id/temp"  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="27ºC 80ºF"
    android:textSize="11sp"
    android:textColor="#EE08B608"
    android:layout_weight="1"/>         
</LinearLayout>

我的小部件闹钟:

public class WidgetAlarm{
    private final int ALARM_ID = 0;
    private final int INTERVAL_MILLIS = 5000;
    private Context mContext;

    public WidgetAlarm(Context context){
        mContext = context;
    }

    public void startAlarm(){      
        Intent alarmIntent = new Intent(CPUWidgetProvider.ACTION_AUTO_UPDATE);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);        
        alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), INTERVAL_MILLIS, pendingIntent); // RTC does not wake the device up
    }

    public void stopAlarm(){
        Intent alarmIntent = new Intent(CPUWidgetProvider.ACTION_AUTO_UPDATE);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
    }
}

我的更新服务:

public class UpdateService extends Service implements SensorEventListener { 
    private SensorManager mSensorManager;
    private Sensor mTempSensor;
    double auxBatteryTemp;
    String systemTemp=null;
    String systemTempFarenheit=null;
    String batteryTemp="25";
    String batteryTempFarenheit="80";
    int processorMaxFreq=-1;
    DecimalFormatSymbols symbols;
    DecimalFormat tempDecimalFormat;
    boolean serviceDestroyed=false;

    Context ctx;
    PowerManager pm;
    RemoteViews remoteViews;
    ComponentName thisWidget;

    private BroadcastReceiver mBatInfoReceiver;     //Broadcast receiver for the battery temperature
    private BroadcastReceiver screenoffReceiver;    //Broadcast receiver for stop the alarm when the screen is off

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {      
        //System.out.println("XXXXX SERVICE START COMMAND");

        /* receivers */     
        mBatInfoReceiver = new BroadcastReceiver(){
            @Override
            public void onReceive(Context arg0, Intent intent) {
                try {
                    auxBatteryTemp = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
                    batteryTemp = ""+(int) (auxBatteryTemp / 10);
                    auxBatteryTemp=auxBatteryTemp/10;
                    batteryTempFarenheit = ""+(int)(((auxBatteryTemp*((float)9/5))+32));
                    updateTemperatures();
                } catch (Exception e) {e.printStackTrace();}
            }
        };  

        screenoffReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                    WidgetAlarm widgetAlarm = new WidgetAlarm(context.getApplicationContext());
                    widgetAlarm.stopAlarm();
                }
                else if(intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
                    WidgetAlarm widgetAlarm = new WidgetAlarm(context.getApplicationContext());
                    widgetAlarm.startAlarm();
                }
                return;
            }
        };

        symbols = new DecimalFormatSymbols(Locale.GERMAN);
        symbols.setDecimalSeparator('.');
        tempDecimalFormat = new DecimalFormat("#.#", symbols);

        this.ctx = this;
        pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE); 
        remoteViews = new RemoteViews(ctx.getPackageName(), R.layout.widget_layout);
        thisWidget = new ComponentName( ctx, CPUWidgetProvider.class );

        //Temperature sensors
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        if (Build.VERSION.SDK_INT >= 14)
            mTempSensor = mSensorManager.getDefaultSensor(13); //13 = Sensor.TYPE_AMBIENT_TEMPERATURE //this constant required Android 4.0 
        else
            mTempSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_TEMPERATURE);

        mSensorManager.registerListener(this, mTempSensor, SensorManager.SENSOR_DELAY_FASTEST);

        //adding receivers
        registerReceiver(this.mBatInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        registerReceiver(screenoffReceiver, filter);

        //return super.onStartCommand(intent, flags, startId);
        return Service.START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        try {
            mSensorManager.unregisterListener(this);
            unregisterReceiver(this.mBatInfoReceiver);
            unregisterReceiver(this.screenoffReceiver);
            serviceDestroyed=true;
        } catch (Exception e) {e.printStackTrace();}
        //System.out.println("XXXXX SERVICE DESTROY");
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }   

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {}

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (Build.VERSION.SDK_INT >= 14){
            if (event.sensor.getType() == 13) { //13 = Sensor.TYPE_AMBIENT_TEMPERATURE //this constant required Android 4.0 
                getTemperature(event);
            }
        }else{
            if (event.sensor.getType() == Sensor.TYPE_TEMPERATURE) {
                getTemperature(event);
            }
        }       
    }

    public void getTemperature(SensorEvent event){
        float values[] = event.values;
        if (values.length>0){
            systemTemp = ""+(int)values[0]; 
            systemTempFarenheit = ""+(int)((values[0]*((float)9/5))+32);
            updateTemperatures();
        }
    }

    public void updateTemperatures(){
        if (pm.isScreenOn() && serviceDestroyed==false){                                                                            
            //si hemos podido obtener la temperatura del sistema, la mostramos, si no, mostramos la temperatura de la bateria.
            if (systemTemp!=null){
                remoteViews.setTextViewText(R.id.temp,systemTemp+"ºC"+" "+systemTempFarenheit+"ºF");
            }
            else{
                remoteViews.setTextViewText(R.id.temp,batteryTemp+"ºC"+" "+batteryTempFarenheit+"ºF");
            }

            AppWidgetManager.getInstance( ctx).updateAppWidget( thisWidget, remoteViews );
            //System.out.println("XXXXX SERVICE UPDATE TEMPERATURES");
        }       
    }
}

我的应用小部件提供商:

public class CPUWidgetProvider extends AppWidgetProvider{           
    public static final String ACTION_WIDGET_CLICK = "ActionWidgetClick"; 
    public static final String ACTION_AUTO_UPDATE = "AUTO_UPDATE";

    DecimalFormatSymbols symbols;
    DecimalFormat tempDecimalFormat;
    int processorMaxFreq=-1;
    String cpuCurrentFreq=null;
    int freq = 0;
    float usage =-1;
    BufferedReader freqBufferedReader;
    PowerManager pm;
    RemoteViews remoteViews;
    ComponentName thisWidget;   
    WidgetAlarm widgetAlarm;
    ActivityManager manager;

    Intent active;
    PendingIntent actionPendingIntent;

    @Override
    public void onDisabled(Context context) {
        super.onDisabled(context);
        context.stopService(new Intent(context, UpdateService.class));

        //stop alarm
        widgetAlarm = new WidgetAlarm(context.getApplicationContext());
        widgetAlarm.stopAlarm();

        //System.out.println("XXXXX onDISABLED");
    }

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

        //System.out.println("XXXXX onDELETED");
    }   

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

        //System.out.println("XXXXX onENABLED");
    }   

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        if (isMyServiceRunning(context)==false){
            context.startService(new Intent(context, UpdateService.class));
        }

        remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        thisWidget = new ComponentName(context, CPUWidgetProvider.class );

        //preparamos el evento para cuando el usuario presiona en el widget, ese evento será recibido en onReceive()
        active = new Intent(context, CPUWidgetProvider.class);
        active.setAction(ACTION_WIDGET_CLICK);
        actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
        remoteViews.setOnClickPendingIntent(R.id.fondo, actionPendingIntent);

        AppWidgetManager.getInstance(context).updateAppWidget( thisWidget, remoteViews );       

        // start alarm
        widgetAlarm = new WidgetAlarm(context.getApplicationContext());
        widgetAlarm.startAlarm();

        //System.out.println("XXXXX onUPDATE");
    }       

    @Override
    public void onReceive(Context context, Intent intent){
        if (intent.getAction().equals(ACTION_WIDGET_CLICK)){
            switchWidget(context);
        }else if(intent.getAction().equals(ACTION_AUTO_UPDATE)){

            symbols = new DecimalFormatSymbols(Locale.GERMAN);
            symbols.setDecimalSeparator('.');
            tempDecimalFormat = new DecimalFormat("#.#", symbols);      

            pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 
            remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            thisWidget = new ComponentName(context, CPUWidgetProvider.class );

            if (pm.isScreenOn()){
                try{                
                    freqBufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"))));
                    freq = Integer.valueOf(freqBufferedReader.readLine()).intValue();   
                    freqBufferedReader.close();
                    freqBufferedReader = null;
                    cpuCurrentFreq=freq / 1000 + " Mhz";                    
                }
                catch (Exception Exception){
                    cpuCurrentFreq=null;
                }                                           

                remoteViews.setTextViewText(R.id.freq,cpuCurrentFreq);

                if (processorMaxFreq==-1)
                    getProcessorMaxFreq();

                usage=(float) (freq/processorMaxFreq);                  
                remoteViews.setTextViewText(R.id.usage,tempDecimalFormat.format(usage / 10.0D)+" %");

                //preparamos el evento para cuando el usuario presiona en el widget, ese evento será recibido en onReceive()
                active = new Intent(context, CPUWidgetProvider.class);
                active.setAction(ACTION_WIDGET_CLICK);
                actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
                remoteViews.setOnClickPendingIntent(R.id.fondo, actionPendingIntent);

                AppWidgetManager.getInstance(context).updateAppWidget( thisWidget, remoteViews );
                //System.out.println("XXXXX UPDATING WIDGET");

            }

        }else{
            super.onReceive(context, intent);
        }

        //System.out.println("XXXXX onRECEIVE: "+intent.getAction());
    }

    public void switchWidget(Context context){
        symbols = new DecimalFormatSymbols(Locale.GERMAN);
        symbols.setDecimalSeparator('.');
        tempDecimalFormat = new DecimalFormat("#.#", symbols);      

        remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        thisWidget = new ComponentName(context.getPackageName(), CPUWidgetProvider.class.getName());
        widgetAlarm = new WidgetAlarm(context.getApplicationContext());
        if (isMyServiceRunning(context)==true){ //si el widget está encendido la apagamos
            context.stopService(new Intent(context, UpdateService.class));          
            widgetAlarm.stopAlarm();
            remoteViews.setTextViewText(R.id.freq,"");
            remoteViews.setTextViewText(R.id.usage,"OFF");
            remoteViews.setTextViewText(R.id.temp,"");
        }
        else{ //si el widget está apagado lo encendemos
            context.startService(new Intent(context, UpdateService.class)); 
            widgetAlarm.startAlarm();
            remoteViews.setTextViewText(R.id.freq,"-----");
            remoteViews.setTextViewText(R.id.usage,"--");
            remoteViews.setTextViewText(R.id.temp,"");
        }
        AppWidgetManager.getInstance(context).updateAppWidget(thisWidget,remoteViews);
    }

    private boolean isMyServiceRunning(Context context) {
        manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (UpdateService.class.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
        return false;
    }

    public void getProcessorMaxFreq(){
        int freq = 0;
        try{            
            freqBufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"))));
            freq = Integer.valueOf(freqBufferedReader.readLine()).intValue();
            freqBufferedReader.close(); 
            freqBufferedReader=null;
            processorMaxFreq=(freq / 1000);                 
        }
        catch (Exception e){e.printStackTrace();}
    }
}

根据 this blog post and the associated bug closed as Working as Intended:

low polling periods are rounded up to 60000ms (one minute)

您会看到 logcat 消息:

Suspiciously short interval 5000 millis; expanding to 60 seconds

当被问及替代方案时,post #9 on the bug 表示:

If you are trying to run more often than every 5 seconds, alarms are the wrong way to go about it. Waking up the device that often is extremely bad for battery life. If you have live UI that needs to be updated continually, use a wakelock and then schedule your activity on a handler. This is actually more battery efficient than setting an alarm every second.

Remember that widgets are intended to run only occasionally; the smallest updatePeriodMillis that the OS will provide is 30 minutes.