Android: 绑定服务无法发送第一条消息,所有其他消息通过。为什么会发生这种情况,如何解决?
Android: bound service can't send first message, all other messages go through. Why does this happen, how to fix it?
我有一个 android activity 和一个相应的服务,其中 activity 只是一个 UI 并且该服务计算要显示的内容。该服务绑定到 activity.
首先我建立一个连接:
在 Activity.java 文件中:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _serviceMessenger = null;
private ServiceConnection _connection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
_serviceMessenger = new Messenger(service);
try
{
// sending the initial welcome message
Message m = Message.obtain(null, ForegroundService.BIND_SERVICE);
m.replyTo = _messenger;
_serviceMessenger.send(m);
}
catch(RemoteException ex)
{
_serviceMessenger = null;
}
}
@Override
public void onServiceDisconnected(ComponentName name)
{
_serviceMessenger = null;
}
};
private static class IncomingHandler extends Handler
{
private WeakReference<MyActivity> _parent;
IncomingHandler(WeakReference<MyActivity> parent)
{
super();
_parent = parent;
}
@Override
public void handleMessage(Message msg)
{
Log.i(LOG_TAG, "Activity: Received message");
MyActivity ta = _parent.get();
switch(msg.what)
{
case ForegroundService.LOCATION_UPDATED:
if(msg.obj == null)
{
Log.e(LOG_TAG, "Activity: msg null");
ta.setTexts("", null, null);
}
else
{
Log.i(LOG_TAG, "Activity: msg ok");
LocWithName loc = (LocWithName)msg.obj;
ta.setTexts(loc.getName(), Double.toString(loc.getHossz()), Double.toString(loc.getSzel()));
Chronometer chronometer = ta.findViewById(R.id.chronom);
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
}
break;
case ForegroundService.LOST_GPS:
ta.setTexts("", "unknown", "unknown");
break;
default:
super.handleMessage(msg);
break;
}
}
}
在 activity onCreate:
Intent startIntent = new Intent(MyActivity.this, ForegroundService.class);
startIntent.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
bindService(startIntent, _connection, Context.BIND_AUTO_CREATE);
startService(startIntent);
并在服务 java 文件中:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _activityMessenger = null;
private static class IncomingHandler extends Handler
{
WeakReference<ForegroundService> _parent;
IncomingHandler(WeakReference<ForegroundService> parent)
{
super();
_parent = parent;
}
@Override
public void handleMessage(Message msg) {
// received welcome message, now we know who to reply to
if(msg.what == ForegroundService.BIND_SERVICE)
{
_parent.get()._activityMessenger = msg.replyTo;
Log.d(LOG_TAG, "received reply address for messenger"); // after 1st edit
}
else
{
super.handleMessage(msg);
}
}
}
private void sendMessageToUI(int message, LocWithName newLoc)
{
Log.i(LOG_TAG, "Service: sending message to UI");
if(_activityMessenger != null)
{
Log.d(LOG_TAG, "messenger not null"); // after 1st edit
try
{
_activityMessenger.send(Message.obtain(null, message, newLoc));
Log.i(LOG_TAG, "Service: message sent");
}
catch(RemoteException ex)
{
// activity is dead
_activityMessenger = null;
}
}
}
然后我开始通过服务的 sendMessageToUI() 函数定期发送消息,即每 5 秒一次。服务的 onStartCommand
运行 是第一个 UI 立即更新,它会为每隔一次迭代重新安排自己。
我知道的:
- 服务中的第一个直接 "UI update" 执行 运行 因为 logcat 向我显示 "sending message to UI" 文本在正确的位置时间
- 所有其他更新 运行
- 所有其他更新都成功传递了它们的消息(这意味着第一个被
_serviceMessenger
停止为空,而不是 RemoteException,因为 catch 块将停止所有后续消息)
- 来自 activity 的欢迎消息到达服务,因为它需要来自服务的进一步回复
我尝试过的:
- 在activity中,先绑定再启动服务(示例代码就是这个状态,一开始是相反的),所以
_activityMessenger
此时不为null必须发送第一条消息
- 发送一条 "burner message" 以便未送达而不是真正重要的消息
- 搜索google类似的问题无果-有问题的地方根本不管用,不只是第一次
- 在此站点搜索类似问题,结果与 google
相同
由于第一条消息和第二条消息之间有五秒的间隔,我怀疑这是初始化速度的问题,但我不能再进一步了。那么这里到底发生了什么,为什么不起作用?
编辑 1:在@pskink 的建议下,我添加了 Log.d()-s。事实证明 activity 仅在 的第一个 运行 之后发送回复地址为 的 "welcome message" UI 更新程序尽管在 startService
.
之前被调用
此外,在@pskink 询问后发送消息的代码:
在役 class:
final Handler handler = new Handler();
Runnable updateUI = new Runnable()
{
// do work to get the information to display
// in this code I set "int message" to one of the constants handled by the activity's IncomingHandler and "LocWithName newLoc" to a useful value or null
sendMessageToUI(message, newLoc);
handler.removeCallbacks(updateUI);
handler.postDelayed(updateUI, 5000);
}
在启动命令服务中:
handler.post(updateUI);
您的错误是假设 bindService()
和 startService()
调用阻塞,直到服务分别为 "bound" 或 "started"。实际情况是 onServiceConnected()
直到 在 onCreate()
returns 之后的某个时间才会被调用。同样,您调用它们的顺序基本上没有意义;在这种情况下,OS 不保证服务将首先或第二处理绑定或 onStartCommand()
。
要修复,请删除对 startService()
的调用(正如@pskink 所建议的);该服务是由于您绑定到它而启动的。 onStartCommand()
将不再被调用。相反,让服务在收到 ForegroundService.BIND_SERVICE
消息时启动 updateUI
Runnable。这允许您建立适当的 "happens before" 关系——即您开始尝试使用 _activityMessenger
发送消息的 ServiceConnection 绑定 "happens before"。
我有一个 android activity 和一个相应的服务,其中 activity 只是一个 UI 并且该服务计算要显示的内容。该服务绑定到 activity.
首先我建立一个连接:
在 Activity.java 文件中:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _serviceMessenger = null;
private ServiceConnection _connection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
_serviceMessenger = new Messenger(service);
try
{
// sending the initial welcome message
Message m = Message.obtain(null, ForegroundService.BIND_SERVICE);
m.replyTo = _messenger;
_serviceMessenger.send(m);
}
catch(RemoteException ex)
{
_serviceMessenger = null;
}
}
@Override
public void onServiceDisconnected(ComponentName name)
{
_serviceMessenger = null;
}
};
private static class IncomingHandler extends Handler
{
private WeakReference<MyActivity> _parent;
IncomingHandler(WeakReference<MyActivity> parent)
{
super();
_parent = parent;
}
@Override
public void handleMessage(Message msg)
{
Log.i(LOG_TAG, "Activity: Received message");
MyActivity ta = _parent.get();
switch(msg.what)
{
case ForegroundService.LOCATION_UPDATED:
if(msg.obj == null)
{
Log.e(LOG_TAG, "Activity: msg null");
ta.setTexts("", null, null);
}
else
{
Log.i(LOG_TAG, "Activity: msg ok");
LocWithName loc = (LocWithName)msg.obj;
ta.setTexts(loc.getName(), Double.toString(loc.getHossz()), Double.toString(loc.getSzel()));
Chronometer chronometer = ta.findViewById(R.id.chronom);
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
}
break;
case ForegroundService.LOST_GPS:
ta.setTexts("", "unknown", "unknown");
break;
default:
super.handleMessage(msg);
break;
}
}
}
在 activity onCreate:
Intent startIntent = new Intent(MyActivity.this, ForegroundService.class);
startIntent.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
bindService(startIntent, _connection, Context.BIND_AUTO_CREATE);
startService(startIntent);
并在服务 java 文件中:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _activityMessenger = null;
private static class IncomingHandler extends Handler
{
WeakReference<ForegroundService> _parent;
IncomingHandler(WeakReference<ForegroundService> parent)
{
super();
_parent = parent;
}
@Override
public void handleMessage(Message msg) {
// received welcome message, now we know who to reply to
if(msg.what == ForegroundService.BIND_SERVICE)
{
_parent.get()._activityMessenger = msg.replyTo;
Log.d(LOG_TAG, "received reply address for messenger"); // after 1st edit
}
else
{
super.handleMessage(msg);
}
}
}
private void sendMessageToUI(int message, LocWithName newLoc)
{
Log.i(LOG_TAG, "Service: sending message to UI");
if(_activityMessenger != null)
{
Log.d(LOG_TAG, "messenger not null"); // after 1st edit
try
{
_activityMessenger.send(Message.obtain(null, message, newLoc));
Log.i(LOG_TAG, "Service: message sent");
}
catch(RemoteException ex)
{
// activity is dead
_activityMessenger = null;
}
}
}
然后我开始通过服务的 sendMessageToUI() 函数定期发送消息,即每 5 秒一次。服务的 onStartCommand
运行 是第一个 UI 立即更新,它会为每隔一次迭代重新安排自己。
我知道的:
- 服务中的第一个直接 "UI update" 执行 运行 因为 logcat 向我显示 "sending message to UI" 文本在正确的位置时间
- 所有其他更新 运行
- 所有其他更新都成功传递了它们的消息(这意味着第一个被
_serviceMessenger
停止为空,而不是 RemoteException,因为 catch 块将停止所有后续消息) - 来自 activity 的欢迎消息到达服务,因为它需要来自服务的进一步回复
我尝试过的:
- 在activity中,先绑定再启动服务(示例代码就是这个状态,一开始是相反的),所以
_activityMessenger
此时不为null必须发送第一条消息 - 发送一条 "burner message" 以便未送达而不是真正重要的消息
- 搜索google类似的问题无果-有问题的地方根本不管用,不只是第一次
- 在此站点搜索类似问题,结果与 google 相同
由于第一条消息和第二条消息之间有五秒的间隔,我怀疑这是初始化速度的问题,但我不能再进一步了。那么这里到底发生了什么,为什么不起作用?
编辑 1:在@pskink 的建议下,我添加了 Log.d()-s。事实证明 activity 仅在 的第一个 运行 之后发送回复地址为 的 "welcome message" UI 更新程序尽管在 startService
.
此外,在@pskink 询问后发送消息的代码:
在役 class:
final Handler handler = new Handler();
Runnable updateUI = new Runnable()
{
// do work to get the information to display
// in this code I set "int message" to one of the constants handled by the activity's IncomingHandler and "LocWithName newLoc" to a useful value or null
sendMessageToUI(message, newLoc);
handler.removeCallbacks(updateUI);
handler.postDelayed(updateUI, 5000);
}
在启动命令服务中:
handler.post(updateUI);
您的错误是假设 bindService()
和 startService()
调用阻塞,直到服务分别为 "bound" 或 "started"。实际情况是 onServiceConnected()
直到 在 onCreate()
returns 之后的某个时间才会被调用。同样,您调用它们的顺序基本上没有意义;在这种情况下,OS 不保证服务将首先或第二处理绑定或 onStartCommand()
。
要修复,请删除对 startService()
的调用(正如@pskink 所建议的);该服务是由于您绑定到它而启动的。 onStartCommand()
将不再被调用。相反,让服务在收到 ForegroundService.BIND_SERVICE
消息时启动 updateUI
Runnable。这允许您建立适当的 "happens before" 关系——即您开始尝试使用 _activityMessenger
发送消息的 ServiceConnection 绑定 "happens before"。