问题
I created a Broadcast Receiver
Which is inside the Service Class
(Because of the Android Broadcast receiver
restrictions Broadcast receiver
stops after while, So I created Background Service
, with foreground Service Notification
Restriction in Android Oreo=< ). This Broadcast Receiver
Listens to Phone Call States Changes IDLE, OFFHOOK, RINGING. According to the Phone call states changes(If the call ends), I get the last call details in the Cursor and Send to that details into the Firebase Realtime Database
. everything works perfectly until I comment(//) on the saveDatafromCursor()
method Which is inside the PhoneStateListener Class
. (I write a Toast message before saving data method). it works very fine. and my service works very well. but if I uncomment my saveDatafromCursor()
method After the call ended. My Service stops(notification icon and toast messages stop. I checked in the real device). Without implementing the Service, I send data to the Firebase it's working very well, Why My service stops if I try to save data to the firebase?
Service Class
public class HammerService extends Service {
@Override
public void onCreate() {
super.onCreate();
IntentFilter ifilter = new IntentFilter();
ifilter.addAction(android.telephony.TelephonyManager.ACTION_PHONE_STATE_CHANGED);
registerReceiver(receiver, ifilter);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Hammer Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_android)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
//do heavy work on a background thread
//stopSelf();
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
String action = intent.getAction();
if(action.equals("android.provider.Telephony.SMS_RECEIVED")){
//action for sms received
}
else if(action.equals(android.telephony.TelephonyManager.ACTION_PHONE_STATE_CHANGED)){
runfirstTime(context,intent);
}
}
};
private void runfirstTime(Context context, Intent intent) {
TelephonyManager telephony = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
MyPhoneStateListener customPhoneListener = new MyPhoneStateListener();
telephony.listen(customPhoneListener, PhoneStateListener.LISTEN_CALL_STATE);
Bundle bundle = intent.getExtras();
String phone_number = bundle.getString("incoming_number");
String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
int state = 0;
if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){
state = TelephonyManager.CALL_STATE_IDLE;
}
else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
state = TelephonyManager.CALL_STATE_OFFHOOK;
}
else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){
state = TelephonyManager.CALL_STATE_RINGING;
}
if (phone_number == null || "".equals(phone_number)) {
return;
}
customPhoneListener.onCallStateChanged(context, state, phone_number);
}
}
PhoneStateListener Class
public class MyPhoneStateListener extends PhoneStateListener {
private static int lastState = TelephonyManager.CALL_STATE_IDLE;
private static Date callStartTime;
private static boolean isIncoming;
static boolean calledAlready = false;
private DatabaseReference databaseReference;
private Context ctx;
public void onCallStateChanged(Context context, int state, String phoneNumber) {
if (lastState == state) {
//No change, debounce extras
return;
}
ctx = context;
System.out.println("Number inside onCallStateChange : " + phoneNumber);
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
isIncoming = true;
callStartTime = new Date();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if (lastState != TelephonyManager.CALL_STATE_RINGING) {
isIncoming = false;
callStartTime = new Date();
}
break;
case TelephonyManager.CALL_STATE_IDLE:
//Went to idle- this is the end of a call. What type depends on previous state(s)
if (lastState == TelephonyManager.CALL_STATE_RINGING) {
//Ring but no pickup- a miss
waitforlastcalllog();
} else if (isIncoming) {
// Toast.makeText(context, "Incoming " + phoneNumber + " Call time " + callStartTime , Toast.LENGTH_SHORT).show();
waitforlastcalllog();
} else {
// Toast.makeText(context, "outgoing " + phoneNumber + " Call time " + callStartTime +" Date " + new Date() , Toast.LENGTH_SHORT).show();
waitforlastcalllog();
}
break;
}
lastState = state;
}
private void waitforlastcalllog() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//String deviceMan = android.os.Build.MANUFACTURER;
String deviceMan = Build.MANUFACTURER;
String deviceName=deviceMan.toLowerCase();
if(deviceName.equals("samsung"))
{
// Toast.makeText(ctx, "dvv "+deviceMan, Toast.LENGTH_SHORT).show();
getCallLogs("samsung");
}
else
{
getCallLogs("other");
}
}
}, 500);
}
private void getCallLogs(String devicename) {
ContentResolver cr = ctx.getContentResolver();
if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
Cursor c = cr.query(CallLog.Calls.CONTENT_URI, null, null, null, null);
if (c != null) {
if(devicename.equals("samsung"))
{
if (c.moveToFirst()) { //starts pulling logs from last - you can use moveToFirst() for first logs
getCallDetails(c);
}
}else
{
if (c.moveToLast()) { //starts pulling logs from last - you can use moveToFirst() for first logs
getCallDetails(c);
}
}
c.close();
}
}
private void getCallDetails(Cursor c)
{
int totalCall=1;
for (int j = 0; j < totalCall; j++) {
String call_id = c.getString(c.getColumnIndexOrThrow(CallLog.Calls._ID));
String phNumber = c.getString(c.getColumnIndexOrThrow(CallLog.Calls.NUMBER));
String callDate = c.getString(c.getColumnIndexOrThrow(CallLog.Calls.DATE));
String callDuration = c.getString(c.getColumnIndexOrThrow(CallLog.Calls.DURATION));
Date dateFormat= new Date(Long.valueOf(callDate));
String callDayTimes = String.valueOf(dateFormat);
String callerName = c.getString(c.getColumnIndexOrThrow(CallLog.Calls.CACHED_NAME));
String direction = null;
switch (Integer.parseInt(c.getString(c.getColumnIndexOrThrow(CallLog.Calls.TYPE)))) {
case CallLog.Calls.OUTGOING_TYPE:
direction = "OUTGOING";
break;
case CallLog.Calls.INCOMING_TYPE:
direction = "INCOMING";
break;
case CallLog.Calls.MISSED_TYPE:
direction = "MISSED";
break;
case CallLog.Calls.VOICEMAIL_TYPE:
direction = "VOICEMAIL_TYPE";
break;
case CallLog.Calls.REJECTED_TYPE:
direction = "REJECTED_TYPE";
break;
case CallLog.Calls.BLOCKED_TYPE:
direction = "BLOCKED_TYPE";
break;
case CallLog.Calls.ANSWERED_EXTERNALLY_TYPE:
direction = "ANS EXT TYPE";
break;
default:
break;
}
c.moveToPrevious(); // if you used moveToFirst() for first logs, you should this line to moveToNext
Toast.makeText(ctx, phNumber + callDuration + callDayTimes + direction +callerName , Toast.LENGTH_LONG).show(); // you can use strings in this line
saveDatafromCursor(phNumber,callDuration,callDayTimes,direction,callerName);
}
}
private void saveDatafromCursor(String phNumber, String callDuration, String callDayTimes, String direction,String callername)
{
ModelCursor mc=new ModelCursor();
mc.setPhoneNumber(phNumber);
mc.setCallDuration(callDuration);
mc.setCallDayTimes(callDayTimes);
mc.setDirection(direction);
if(callername==null)
{
callername="Not in Contacts";
}
mc.setCallUsername(callername);
if (!calledAlready) {
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
calledAlready = true;
}
FirebaseDatabase database = FirebaseDatabase.getInstance();
databaseReference = database.getInstance().getReference().child("Call-Details-X");
String key = databaseReference.push().getKey();
String android_id = Settings.Secure.getString(ctx.getContentResolver(),
Settings.Secure.ANDROID_ID);
Date date=new Date();
SimpleDateFormat timeStampFormatsearch = new SimpleDateFormat("dd-yyyy-MM");
String dateofcall = timeStampFormatsearch.format(date);
databaseReference.child(android_id).child(dateofcall).child(key).setValue(mc).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
}
});
}
}
Logcat - Error
2020-08-12 10:03:33.217 5151-5151/com.fooding.callhammer E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.fooding.callhammer, PID: 5151
com.google.firebase.database.DatabaseException: Calls to setPersistenceEnabled() must be made before any other usage of FirebaseDatabase instance.
at com.google.firebase.database.FirebaseDatabase.assertUnfrozen(com.google.firebase:firebase-database@@17.0.0:316)
at com.google.firebase.database.FirebaseDatabase.setPersistenceEnabled(com.google.firebase:firebase-database@@17.0.0:284)
at com.fooding.callhammer.MyPhoneStateListener.saveDatafromCursor(MyPhoneStateListener.java:214)
at com.fooding.callhammer.MyPhoneStateListener.getCallDetails(MyPhoneStateListener.java:191)
at com.fooding.callhammer.MyPhoneStateListener.getCallLogs(MyPhoneStateListener.java:138)
at com.fooding.callhammer.MyPhoneStateListener.access$000(MyPhoneStateListener.java:26)
at com.fooding.callhammer.MyPhoneStateListener$1.run(MyPhoneStateListener.java:104)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2020-08-12 10:03:33.446 2016-2122/system_process E/InputDispatcher: channel '276f02e com.fooding.callhammer/com.fooding.callhammer.MainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!
2020-08-12 10:03:36.556 2016-2197/system_process E/TaskPersister: File error accessing recents directory (directory doesn't exist?).
2020-08-12 10:03:48.799 2016-2041/system_process E/memtrack: Couldn't load memtrack module
2020-08-12 10:03:49.499 2016-2048/system_process E/BatteryExternalStatsWorker: no controller energy info supplied for wifi
2020-08-12 10:04:00.018 2016-2041/system_process E/memtrack: Couldn't load memtrack module
回答1:
From documentation: A service runs in the main thread of its hosting process; the service does not create its own thread and does not run in a separate process unless you specify otherwise. You should run any blocking operations on a separate thread within the service to avoid Application Not Responding (ANR) errors. I think that's the reason why you can not do "heavy" work within Service class. I didn't go through your code, but you can do two things:
1.) Make new thread inside service on create and do "heavy work"(don' forget to stop it). https://developer.android.com/guide/components/services
2.) extend JobIntentService class, give it a try. And then override function onHandleWork(intent), where you start "heavy" work. https://developer.android.com/reference/androidx/core/app/JobIntentService
来源:https://stackoverflow.com/questions/63350856/cant-do-heavy-work-on-broadcast-receiver-which-is-inside-the-service