Làm thế nào để luôn chạy một dịch vụ trong nền?


76

Tôi đang trong quá trình tạo một ứng dụng tương tự như ứng dụng SMS tích hợp sẵn.

Tôi cân gi:

  • một dịch vụ luôn chạy trong nền
  • 5 phút một lần. dịch vụ kiểm tra vị trí hiện tại của thiết bị và gọi một dịch vụ web
  • nếu đáp ứng các tiêu chí nhất định, dịch vụ sẽ tạo thông báo (giống như ứng dụng SMS)
  • khi thông báo được nhấp, người dùng sẽ được đưa đến ứng dụng (giống như ứng dụng SMS)
  • khi ứng dụng được cài đặt, dịch vụ sẽ được khởi động
  • khi thiết bị được khởi động lại, dịch vụ sẽ được bắt đầu

Những gì tôi đã thử:
- chạy một dịch vụ thông thường hoạt động tốt cho đến khi Android loại bỏ dịch vụ
- sử dụng AlarmManager để thực hiện 5 phút. cuộc gọi ngắt quãng tới một dịch vụ. Nhưng tôi đã không thể làm cho việc này thành công.

Câu trả lời:


34

một dịch vụ luôn chạy trong nền

Điều này là không thể theo bất kỳ nghĩa thực tế nào của thuật ngữ này, như bạn đã khám phá ra. Nó cũng là thiết kế xấu .

5 phút một lần. dịch vụ kiểm tra vị trí hiện tại của thiết bị và gọi một dịch vụ web

Sử dụng AlarmManager.

sử dụng AlarmManager thực hiện trong 5 phút. cuộc gọi ngắt quãng tới một dịch vụ. Nhưng tôi đã không thể làm cho việc này thành công.

Đây là một dự án mẫu cho thấy cách sử dụng một dự án , cùng với việc sử dụng WakefulIntentServiceđể bạn tỉnh táo trong khi cố gắng thực hiện toàn bộ dịch vụ Web.

Nếu bạn tiếp tục gặp vấn đề với nó, hãy mở ra một câu hỏi mới về những điều cụ thể mà bạn đang gặp phải AlarmManagerđang khiến bạn đau buồn.


Tôi đã sử dụng dự án mẫu này và tôi đặt nhật ký và đợi 10 phút, không có gì hoạt động? tôi phải gọi bất cứ dịch vụ nào để bắt đầu nó?
Samir Mangroliya

2
@SamirMangroliya: Nếu bạn nhìn vào ngày tháng trên câu trả lời, bạn sẽ nhận thấy rằng nó đã hơn 3 năm tuổi. Ví dụ này được viết cho Android 2.x và bạn sẽ cần khởi động lại thiết bị / trình giả lập để bắt đầu báo thức. Bây giờ, đối với Android 3.1+, ngay cả điều đó vẫn chưa đủ - bạn cần phải chạy một hoạt động trước khi bộ thu khởi động có hiệu lực. Tôi khuyên bạn nên chuyển sang github.com/commonsguy/cw-omnibus/tree/master/AlarmManager/…. Đây là dự án cơ bản tương tự, nhưng cập nhật hơn. Chạy nó và đảm bảo hoạt động chạy (hiển thị a Toast), và các báo thức sẽ bắt đầu.
CommonsWare

xin chào nếu tôi gọi PollReceiver.scheduleAlarms (this); trong hoạt động chính và khi tôi đóng ứng dụng rồi khởi động lại (Giả sử trong một giờ tôi mở ứng dụng hơn 15 lần) thì nó tạo báo thức mỗi lần trong 5 phút?
Samir Mangroliya

1
@SamirMangroliya: Lập lịch báo thức cho một thời điểm tương đương PendingIntentsẽ hủy mọi báo thức hiện có cho điều đó PendingIntent. Ngoài ra, xin lưu ý rằng đây là một ví dụ từ một cuốn sách và nó cố tình không cố gắng giải quyết tất cả các tình huống.
CommonsWare

Tôi đã tìm thấy bài đăng sau, nói rằng điều này có thể xảy ra: blogmobile.itude.com/2012/02/20/… . Cách tiếp cận đó có hiệu quả không?
Đánh dấu Vincze

26

Một trong những ứng dụng của tôi làm điều gì đó rất giống. Để đánh thức dịch vụ sau một khoảng thời gian nhất định, tôi khuyên bạn nênpostDelayed()

Có một trường xử lý:

private final Handler handler = new Handler();

và bồi bổ Runnable

private final Runnable refresher = new Runnable() {
  public void run() {
    // some action
  }
};

Bạn có thể kích hoạt Thông báo của mình ở chế độ chạy được.

Khi xây dựng dịch vụ và sau mỗi lần thực thi, hãy bắt đầu như vậy:

handler.postDelayed(refresher, /* some delay in ms*/);

Trong onDestroy()xóa bài đăng

handler.removeCallbacks(refresher);

Để bắt đầu dịch vụ lúc khởi động, bạn cần có bộ khởi động tự động. Điều này có trong tệp kê khai của bạn

<receiver android:name="com.example.ServiceAutoStarter">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
  </receiver>

ServiceAutoStartertrông như thế này:

public class ServiceAutoStarter extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    context.startService(new Intent(context, UpdateService.class));
  }
}

Việc ngăn chặn hệ điều hành giết chết dịch vụ là một việc khó khăn. Ngoài ra, ứng dụng của bạn có thể gặp lỗi RuntimeExceptionvà bị treo, hoặc logic của bạn có thể bị đình trệ.

Trong trường hợp của tôi, nó có vẻ hữu ích khi luôn làm mới dịch vụ trên màn hình với a BroadcastReceiver. Vì vậy, nếu chuỗi cập nhật bị đình trệ, nó sẽ được phục hồi khi người dùng sử dụng điện thoại của họ.

Trong dịch vụ:

private BroadcastReceiver screenOnReceiver; 

Trong dịch vụ của bạn onCreate()

screenOnReceiver = new BroadcastReceiver() {

  @Override
  public void onReceive(Context context, Intent intent) {
    // Some action
  }
};

registerReceiver(screenOnReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));

Sau đó, hủy đăng ký dịch vụ của bạn onDestroy()với

unregisterReceiver(screenOnReceiver);

2
Tôi thường thích câu trả lời của bạn, nhưng câu này ... không nhiều lắm. :-( Ví dụ: bạn đang đề xuất rằng dịch vụ đăng ký một máy thu để tự phục hồi sau khi bị giết. Máy thu sẽ bị giết cùng với dịch vụ; do đó, điều này sẽ không có tác dụng. Hơn nữa, toàn bộ khái niệm về một dịch vụ vĩnh cửu thật tệ hại trên Android và là lý do khiến người dùng chống trả lại các trình diệt tác vụ hoặc màn hình Dịch vụ đang chạy trong ứng dụng Cài đặt. Có rất ít trường hợp cần một dịch vụ thực sự trường tồn - phần lớn thời gian là AlarmManagerđủ.
CommonsWare

4
Cảm ơn CWare, tôi có lẽ nên nói rõ hơn rằng trình phục hồi là để bảo vệ khỏi lỗi logic và nhiều thứ có thể làm ngưng trệ chuỗi sự kiện đánh thức dịch vụ, vì những gì được cho là do hệ thống đóng dịch vụ thường có thể là thứ khác. Tôi sẽ xem xét cách tiếp cận của trình quản lý báo thức, tôi mơ hồ nhớ lại đã thử cách này một thời gian trước và không thể làm cho nó hoạt động.
Jim Blackler

3

Bạn có thể làm điều này bằng một số triển khai đơn giản:

public class LocationTrace extends Service implements LocationListener{

    // The minimum distance to change Updates in meters
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters
    private static final int TWO_MINUTES = 100 * 10;

    // The minimum time between updates in milliseconds
    private static final long MIN_TIME_BW_UPDATES = 1000 * 10; // 30 seconds

    private Context context;

    double latitude;
    double longitude;

    Location location = null;
    boolean isGPSEnabled = false;
    boolean isNetworkEnabled = false;
    protected LocationManager locationManager;


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        this.context = this;
        get_current_location();
//      Toast.makeText(context, "Lat"+latitude+"long"+longitude,Toast.LENGTH_SHORT).show();
        return START_STICKY;
    }


    @Override
    public void onLocationChanged(Location location) {
        if((location != null) && (location.getLatitude() != 0) && (location.getLongitude() != 0)){

            latitude = location.getLatitude();
            longitude = location.getLongitude();

            if (!Utils.getuserid(context).equalsIgnoreCase("")) {
                Double[] arr = { location.getLatitude(), location.getLongitude() };

               // DO ASYNCTASK
            }
        }

    }


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

    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {

    }

    /*
    *  Get Current Location
    */
    public Location get_current_location(){

        locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

        isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

        isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

        if(!isGPSEnabled && !isNetworkEnabled){



        }else{
            if (isGPSEnabled) {

                if (location == null) {
                    locationManager.requestLocationUpdates(
                            LocationManager.GPS_PROVIDER,
                            MIN_TIME_BW_UPDATES,
                            MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

                    if (locationManager != null) {
                        location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                        if (location != null) {
                            latitude = location.getLatitude();
                            longitude = location.getLongitude();
        //                  Toast.makeText(context, "Latgps"+latitude+"long"+longitude,Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }
            if (isNetworkEnabled) {

                locationManager.requestLocationUpdates(
                        LocationManager.NETWORK_PROVIDER,
                        MIN_TIME_BW_UPDATES,
                        MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

                if (locationManager != null) {

                    if (location != null) {
                        latitude = location.getLatitude();
                        longitude = location.getLongitude();
        //              Toast.makeText(context, "Latgps1"+latitude+"long"+longitude,Toast.LENGTH_SHORT).show();
                    }
                }
            }
        }

        return location;
    }


    public double getLatitude() {
        if(location != null){
            latitude = location.getLatitude();
        }
        return latitude;
    }

    public double getLongitude() {
         if(location != null){
             longitude = location.getLongitude();
         }

        return longitude;
    }


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

    @Override
    public void onDestroy() {
        if(locationManager != null){
            locationManager.removeUpdates(this);
        }
        super.onDestroy();
    }

}

Bạn có thể bắt đầu dịch vụ bằng cách này:

/*--Start Service--*/
startService(new Intent(Splash.this, LocationTrace.class));

Trong tệp kê khai:

 <service android:name=".LocationTrace">
            <intent-filter android:priority="1000">
                <action android:name="android.location.PROVIDERS_CHANGED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
  </service>

1

bằng ba bước sau, bạn có thể đánh thức 5 phút một lần hầu hết các thiết bị Android:

1. đặt AlarmManager thay thế của bạn cho các API khác nhau:

AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(getApplicationContext(), OwnReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(getApplicationContext(), 0, i, 0);

if (Build.VERSION.SDK_INT >= 23) {
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
}
else if (Build.VERSION.SDK_INT >= 19) {
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
} else {
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
}

2. xây dựng BroadcastReceiver tĩnh của riêng bạn:

public static class OwnReceiver extends BroadcastReceiver {

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

       //do all of your jobs here

        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent i = new Intent(context, OwnReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);

        if (Build.VERSION.SDK_INT >= 23) {
            am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
        }
        else if (Build.VERSION.SDK_INT >= 19) {
            am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
        } else {
            am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
        }
    }
}

3. thêm <receiver>vào AndroidManifest.xml:

<receiver android:name=".OwnReceiver"  />

1
Bộ thu tĩnh không thể được khởi tạo trong Tệp kê khai như thế này, thay vào đó bạn nên sử dụng <receiver android: name = "className $ OwnReceiver" />
Psycho

0

Theo tôi khi bạn muốn dịch vụ của mình luôn chạy có nghĩa là nó sẽ không bị dừng lại khi ứng dụng bị giết, bởi vì nếu ứng dụng của bạn đang chạy hoặc ở chế độ nền thì dịch vụ của bạn sẽ chạy. Khi bạn tắt ứng dụng trong khi dịch vụ đang chạy, chức năng onTaskRemoved được kích hoạt.

@Override
        public void onTaskRemoved(Intent rootIntent) {
            Log.d(TAG, "onTaskRemoved: removed");
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(System.currentTimeMillis() + 10000);
((AlarmManager) getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), PendingIntent.getService(getApplicationContext(), 0, new Intent(getApplicationContext(), RegisterReceiverService.class), 0));
            super.onTaskRemoved(rootIntent);
        }

Vì vậy, về cơ bản sau khi bạn hủy ứng dụng, dịch vụ sẽ được khởi động sau 10 giây. Lưu ý: Trong trường hợp bạn muốn sử dụng

AlarmManager.ELAPSED_REALTIME_WAKEUP

thời gian tối thiểu sau đó bạn có thể khởi động lại dịch vụ sẽ là 15 phút.

Hãy nhớ rằng bạn cũng cần khởi động dịch vụ khi khởi động lại bằng BroadcastReceiver.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.