即使锁定设备后,是否可以连续在Android平台上运行服务?

时间:2016-04-04 09:38:07

标签: android android-studio broadcastreceiver android-service

我们一直致力于为Android平台开发服务。

在我们的服务中,我们需要每隔一分钟将设备的GPS数据(Lat和Long)发送到某些外部REST服务。

锁定设备后,它运行正常了将近15分钟。但之后它不会发送任何数据。

解锁设备后,它再次开始通过REST服务发送数据。

我的代码到目前为止

public class MainActivity extends AppCompatActivity {

private PendingIntent pendingIntent;
private PowerManager.WakeLock wakeLock;

public static final String USER_NAME = "USERNAME";

String username;
String password;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Intent alarm = new Intent(this, AlarmReceiver.class);
    boolean alarmRunning = (PendingIntent.getBroadcast(this, 0, alarm, PendingIntent.FLAG_NO_CREATE) != null);
    if(alarmRunning == false) {
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarm, 0);
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), 30000, pendingIntent);
    }

    PowerManager mgr = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
    wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakeLock");
    wakeLock.acquire();
 }

public class BackgroundService extends Service  {

private boolean isRunning;
private Context context;
private Thread backgroundThread;

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

@Override
public void onCreate() {
    this.context = this;
    this.isRunning = false;
    this.backgroundThread = new Thread(myTask);
}

private Runnable myTask = new Runnable() {
    public void run() {
        // Do something here
        login("admin","admin");
        stopSelf();
    }
};

@Override
public void onDestroy() {
    this.isRunning = false;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if(!this.isRunning) {
        this.isRunning = true;
        this.backgroundThread.start();
    }
    return START_STICKY;
}

private void login(final String strLatitude, final String strLongitude) {


    class LoginAsync extends AsyncTask<String, Void, String> {

        String charset = "UTF-8";
        HttpURLConnection conn;
        DataOutputStream wr;
        StringBuilder result = new StringBuilder();
        URL urlObj;
        JSONObject jObj = null;
        StringBuilder sbParams;
        String paramsString;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // loadingDialog = ProgressDialog.show(MainActivity.this, "Please wait", "Loading...");
        }

        @Override
        protected String doInBackground(String... params) {
            String uname = params[0];
            String pass = params[1];

            sbParams = new StringBuilder();

            try {
                sbParams.append("name").append("=")
                        .append(URLEncoder.encode(uname, charset));
                sbParams.append("&");
                sbParams.append("password").append("=")
                        .append(URLEncoder.encode(pass, charset));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            try {

                String url="http://192.168.0.122:1234/YegoService.svc/AddVehicleMovement";
                URL object=new URL(url);

                HttpURLConnection con = (HttpURLConnection) object.openConnection();
                con.setDoOutput(true);
                con.setDoInput(true);
                con.setRequestProperty("Content-Type", "application/json");
                con.setRequestProperty("Accept", "application/json");
                con.setRequestMethod("POST");

                JSONObject parent = new JSONObject();

                parent.put("strValidatorID","111");
                parent.put("TXT_LAT", "28.25252525");

                parent.put("TXT_LONG", "77.7777777");
                parent.put("DAT_DATE", "");
                con.connect();

                OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
                wr.write(parent.toString());
                wr.flush();
                wr.close();

                InputStream input = con.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(input));
                String line;

                while ((line = reader.readLine()) != null) {
                    result.append(line);
                }

                con.disconnect();

            } catch (IOException e) {
                e.printStackTrace();
            }
            catch (Exception ex) {
                ex.printStackTrace();
            }

            return result.toString();
        }

        @Override
        protected void onPostExecute(String result){
            String s = result.trim();
        }
    }

    LoginAsync la = new LoginAsync();
    la.execute("admin", "admin");

}

}

public class AlarmReceiver extends BroadcastReceiver {
String strLatitude;
String strLongitude;

@Override
public void onReceive(Context context, Intent intent) {
    Intent background = new Intent(context, BackgroundService.class);
    context.startService(background);
}
}

怎么办?

8 个答案:

答案 0 :(得分:4)

您正在Activity获取唤醒锁定。这里的问题是当设备被锁定时,您的Activity会被推到后台。在15分钟不活动之后,Android正在简单地终止进程。这会释放唤醒锁定。设备进入睡眠状态。

现在,下次闹钟响起时,设备会唤醒,BroadcastReceiver被触发,onReceive()被调用,它会启动Service,但设备会返回因为没有唤醒锁定而睡觉,所以“服务不做任何事情。”

另一种方法是,如果您想在应用程序运行时阻止手机进入睡眠状态,则可以获取Service中的唤醒锁定。在这种情况下,您不希望每次stopSelf()运行时都致电Runnable。您希望让Service保持运行直至您想要停止它,此时您可以拨打stopService()。这样,Service将始终处于活动状态(即使它没有做任何事情),它会阻止设备通过唤醒锁休眠。但是,这可能会对电池造成不可接受的消耗(您必须对其进行测试)。

您需要在BroadcastReceiver中获取唤醒锁,并确保Service启动并在设备恢复睡眠之前获取唤醒锁定。查看WakefulBroadcastReceiver,您可以使用它来实现此行为。

答案 1 :(得分:4)

一种方法可能是您依赖AlarmManager:一旦您订阅了AlarmManager,系统本身就会按照您设置的时间间隔运行您的代码,即使您的应用程序未处于活动状态。每次运行时,您都可以决定处理一些代码......这样您就可以完全避免使服务保持活跃状态​​。

您需要的是一个将处理AlarmManager意图的Alarm类。

创建闹钟:

public class Alarm extends BroadcastReceiver 
{

    private static final String TAG = "Alarm";

    @Override
    public void onReceive(Context context, Intent intent) 
    {   
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
        wl.acquire();

        /***************
        Here you can do your stuff...
        This will be triggered every second. 
        Send data from here, or better: call an IntentService
        that will take care of it.
        ****************/

        wl.release();
    }

    public void SetAlarm(Context context)
    {
        Intent i = new Intent(context, Alarm.class);

        boolean alarmUp = (PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_NO_CREATE) != null);

        if (alarmUp)
        {
            // The alarm is already running, do not set it twice
        }
        else
        {
            AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
            am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000, pi); // 1000 Millisec means it will trigger it every second... and RTC_WAKEUP means that it will wake up your device if needed.
        }
    }

    // later on, use this method if you want to manually cancel the AlarmManager :

    public void CancelAlarm(Context context)
    {
        Intent intent = new Intent(context, Alarm.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(sender);
    }
}

在你的Manifest中声明这个Alarm BroadcastReceiver

<receiver 
    android:name=".utils.Alarm"
    android:process=":remote" >
</receiver>

在你的Activity中你想要的地方调用这个AlarmManager!

Alarm alarm = new Alarm();

@Override
protected void onCreate(Bundle savedInstanceState) 
{

    alarm.SetAlarm(this);

}

// or if elsewhere you need to stop the Alarm :
alarm.CancelAlarm(this);

这是主要的想法。 现在你需要打开或关闭屏幕。 对于这两个解决方案:您可以注册设备屏幕状态意图并管理AlarmManager打开/关闭...或者您可以让AlarmManager始终运行但在发送数据之前检查设备是否打开/关闭...

希望这会有所帮助!

答案 2 :(得分:1)

是的,即使设备已锁定,您也可以运行任何服务。甚至,您可以在重启设备后恢复服务。

您可以实施GCM网络管理器。

所需的示例代码: -

<service
    android:name=".MyTaskService"
    android:exported="true"
    android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE">
    <intent-filter>
        <action android:name="com.google.android.gms.gcm.ACTION_TASK_READY" />
    </intent-filter>
</service>

Java代码: -

mGcmNetworkManager = GcmNetworkManager.getInstance(this);
OneoffTask task = new OneoffTask.Builder()
        .setService(MyTaskService.class)
        .setTag(TASK_TAG_WIFI)
        .setExecutionWindow(0L, 3600L)
        .setRequiredNetwork(Task.NETWORK_STATE_UNMETERED)
        .build();

mGcmNetworkManager.schedule(task);

更多信息,您可以访问https://developers.google.com/cloud-messaging/network-manager#run_tasks并阅读文档。

您只需在项目中包含gcm服务即可使用GCM网络管理器。支持4.0 +

如果这是您想要的解决方案,请接受此答案。这也可以帮助其他开发人员。

答案 3 :(得分:1)

是的,你可以实现几乎永远不会被杀死的后台服务。但是你必须声明它在前台运行。您可以在本文(http://developer.android.com/guide/components/services.html)中引用此网址(http://developer.android.com/guide/components/processes-and-threads.html)来了解Android开发者网站所说的内容,

重要性层次结构中有五个级别,按重要性顺序排列不同类型的流程(第一个流程最重要,最后被杀死):

  1. 前台流程
  2. 用户当前正在执行的操作所需的过程。如果满足以下任一条件,则认为进程处于前台:

    • 它托管用户正在与之交互的活动( 调用了Activity的onResume()方法。
    • 它托管一个绑定到用户正在与之交互的活动的服务。
    • 它托管一个“在前台”运行的服务 - 该服务调用了startForeground()。
    • 它托管正在执行其生命周期回调之一的服务 (onCreate(),onStart()或onDestroy())。
    • 它托管正在执行其onReceive()的BroadcastReceiver 方法

    通常,在任何给定时间只存在少数前台进程。它们只是作为最后的手段而被杀死 - 如果内存太低而无法继续运行。通常,此时设备已达到内存分页状态,因此需要终止某些前台进程以保持用户界面的响应。

    所以你必须在前台开始服务。为此,您必须实施以下服务。

    public class MyForegroundService extends Service {
    
        @Override
        public void onCreate() {
            super.onCreate();
            //your code goes here
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            keepServiceAlive();
            //your code goes here
            return(START_NOT_STICKY);
        }
    
        private void keepServiceAlive() {
            Intent notificationIntent = new Intent(this, MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
    
            Notification notification = new NotificationCompat.Builder(this).setContentTitle(getString(R.string.app_name))
                    .setContentText("Hello")
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setContentIntent(pendingIntent)
                    .build();
            startForeground(Notification.FLAG_ONGOING_EVENT, notification);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.w(getClass().getName(), "Got to stop()!");
            stopForeground(true);
        }
    }
    

    谢谢你,好运......

答案 4 :(得分:1)

当服务完成运行时,您必须反复触发警报。

此外,您还可以实现在设备启动时启动服务的BroadCastReceiver。

查看本教程: http://ncona.com/2014/04/schedule-your-android-app-to-do-something-periodically/

答案 5 :(得分:1)

我在我的应用程序中遇到了同样的问题,但我解决了我的问题,首先创建服务,使用定期服务。您可以指定更新数据的时间限制。就我而言,这是代码。

<强> UpdateService.java

public class UpdateServices extends Service implements LocationListener {

    String id, latee, longee;
    // j
    private ProgressDialog pDialog;
    ProgressDialog progressDialog;
    JSONParser jsonParser = new JSONParser();
    DBManager db;
    private static String url_create_locationupdate = "http://192.168.0.175/simple_demo3/classes/create_locationupdate.php";
    private static final String TAG_SUCCESS = "success";
    public static String LOG = "Log";
    private final Context mContext;
    boolean isGPSEnabled = false;
    boolean isNetworkEnabled = false;
    boolean canGetLocation = false;
    Location location; // location
    double latitude; // latitude
    double longitude; // longitude
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 3; // 0 meters
    private long MIN_TIME_BW_UPDATES; // 10 second
    private long MIN_LENGTH_BW_UPDATES;
    SharedPreferences mPref;
    protected LocationManager locationManager;

    public UpdateServices(Context context) {
        this.mContext = context;
    }

    public UpdateServices() {
        super();
        mContext = UpdateServices.this;
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        Log.i(LOG, "Service started");

        mPref = getSharedPreferences("mFile", 0);
        MIN_TIME_BW_UPDATES = mPref.getLong("mint", 1) * 1000 * 60;
        MIN_LENGTH_BW_UPDATES = mPref.getLong("kmeter", 1) * 1000;
        Log.i("asd", "This is sparta");
        latitude = getLocation().getLatitude();
        longitude = getLocation().getLongitude();

        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(LOG, "Service created");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(LOG, "Service destroyed");

    }

    public Location getLocation() {
        try {
            locationManager = (LocationManager) mContext
                    .getSystemService(LOCATION_SERVICE);

            isGPSEnabled = locationManager
                    .isProviderEnabled(LocationManager.GPS_PROVIDER);
            isNetworkEnabled = locationManager
                    .isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            if (!isGPSEnabled && !isNetworkEnabled) {
            } else {
                this.canGetLocation = true;
                if (isNetworkEnabled) {
                    locationManager.requestLocationUpdates(
                            LocationManager.NETWORK_PROVIDER, 5000,
                            MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
                    Log.d("Network", "Network");
                    if (locationManager != null) {
                        location = locationManager
                                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                        if (location != null) {
                            latitude = location.getLatitude();
                            longitude = location.getLongitude();
                        }
                    }
                }
                // if GPS Enabled get lat/long using GPS Services
                if (isGPSEnabled) {
                    if (location == null) {
                        locationManager.requestLocationUpdates(
                                LocationManager.GPS_PROVIDER,
                                MIN_TIME_BW_UPDATES,
                                MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
                        Log.d("GPS Enabled", "GPS Enabled");
                        if (locationManager != null) {
                            location = locationManager
                                    .getLastKnownLocation(LocationManager.GPS_PROVIDER);
                            if (location != null) {
                                latitude = location.getLatitude();
                                longitude = location.getLongitude();
                            }
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return location;
    }

    @Override
    public void onLocationChanged(Location location) {
        // this will be called every second
        String laty = Double.toString(getLocation().getLatitude());
        String lagy = Double.toString(getLocation().getLongitude());
        db = new DBManager(mContext);
        db.open();
        db.mInsertGPSCor(laty, lagy);
        Toast.makeText(
                getApplicationContext(),
                "Your Location is - \nLat: " + location.getLatitude()
                        + "\nLong: " + location.getLongitude(),
                Toast.LENGTH_LONG).show();

        Toast.makeText(UpdateServices.this, "record entered",
                Toast.LENGTH_SHORT).show();
        db.close();
// store in server
        new CreateNewProduct(this).execute();

    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {

    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {

    }

    class CreateNewProduct extends AsyncTask<String, String, String> {
        private Context mContext;

        public CreateNewProduct(Context context) {
            super();
            mContext = context;
        }

        @Override
        protected void onPreExecute() {
            try {
                super.onPreExecute();
                progressDialog = ProgressDialog.show(mContext,
                        "Press Back to Cancel", "Sending Data to Server..",
                        true, false);
            } catch (Exception e) {
                // TODO: handle exception
            }

        }

        /**
         * Creating product
         * */
        protected String doInBackground(String... args) {
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair("ID", id));
            params.add(new BasicNameValuePair("LATITUDE", latee));
            params.add(new BasicNameValuePair("LONGITUDE", longee));

            JSONObject json = jsonParser.makeHttpRequest(
                    url_create_locationupdate, "POST", params);

            try {
                int success = json.getInt(TAG_SUCCESS);

                if (success == 1) {

                    return "done";
                } else {
                    // failed to create product
                    return "fail";
                }
            } catch (JSONException e) {
                e.printStackTrace();
                return "exec";
            }

        }

        /**
         * After completing background task Dismiss the progress dialog
         * **/
        protected void onPostExecute(String file_url) {
            if (progressDialog.isShowing())
                progressDialog.dismiss();

            if (file_url.equalsIgnoreCase("done")) {

                show.message(mContext, "uploading successed");
            }
            if (file_url.equalsIgnoreCase("fail")
                    || file_url.equalsIgnoreCase("exec")) {
                try {
                    show.message(mContext, "uploading failed");
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    public void onConnectionSuspended(int arg0) {
        // TODO Auto-generated method stub

    }

}

和Main.java

public class Main extends Activity {

    Button btn_startGps, btn_stopGps;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.auto_gps_update);
        btn_startGps = (Button) findViewById(R.id.button_service);
        btn_stopGps = (Button) findViewById(R.id.button_stopservice);


        btn_startGps.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {           
                startService(new Intent(About.this, UpdateServices.class));
                Toast.makeText(About.this, "Service Started",
                        Toast.LENGTH_SHORT).show();
            }
        });

        btn_stopGps.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                stopService(new Intent(About.this, UpdateServices.class));
            Log.e("sss", "ddddd");
                Toast.makeText(About.this, "Service Stopped",
                        Toast.LENGTH_SHORT).show();

            }
        });
}
  

但是这里的问题服务并不止于此以停止服务

因为我已经回来了

 return START_STICKY;
onStartCommand(...)

中的

START_STICKY and START_NOT_STICKY

了解详情

Official docs

答案 6 :(得分:1)

如果您在API 21+上运行应用,则使用Google文档中描述的JobScheduler也是最佳方法。

此外,如果您不想更改代码结构,即使屏幕关闭,您也可以使用您的服务来保持CPU开启状态。阅读Google文档中to keep CPU On的方式。只需在您的清单<uses-permission android:name="android.permission.WAKE_LOCK" />Service.onCreate中添加权限,即可:

PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
        "MyWakelockTag");
wakeLock.acquire();

使用Service.onDestroywakelock.release()中发布。 但要注意它会耗尽你的电池。但如果你说该设备将始终插入源电源,我认为这不会是一个问题。为了以防万一,最好在应用程序中设置管理UI以手动停止服务。

答案 7 :(得分:1)

In Manifest file,
<service android:name=".MyService"></service>

MyService.java

public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
       // your code here
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Intent it = new Intent(MyService.this, MyService.class);
        getApplication().startService(it); // If service will destroy, Start the service again
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }


}

运行该服务,将其添加到您的活动

    Intent it = new Intent(getApplication(), MyService.class);
getApplicationContext().startService(it);