ChatGPT解决这个技术问题 Extra ChatGPT

示例:使用消息传递在 Activity 和 Service 之间进行通信

我找不到任何关于如何在活动和服务之间发送消息的示例,而且我花了太多时间来弄清楚这一点。这是一个示例项目供其他人参考。

此示例允许您直接启动或停止服务,并分别与服务绑定/取消绑定。当服务运行时,它会以 10 Hz 递增一个数字。如果 Activity 绑定到 Service,它将显示当前值。数据以整数和字符串的形式传输,因此您可以了解如何以两种不同的方式进行传输。活动中还有用于向服务发送消息的按钮(更改增量值)。

截屏:

https://i.stack.imgur.com/CMPS7.png

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.exampleservice"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    <service android:name=".MyService"></service>
    </application>
    <uses-sdk android:minSdkVersion="8" />
</manifest>

res\values\strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">ExampleService</string>
    <string name="service_started">Example Service started</string>
    <string name="service_label">Example Service Label</string>
</resources>

资源\布局\main.xml:

<RelativeLayout
    android:id="@+id/RelativeLayout01"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Service" >
    </Button>

    <Button
        android:id="@+id/btnStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="Stop Service" >
    </Button>
</RelativeLayout>

<RelativeLayout
    android:id="@+id/RelativeLayout02"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <Button
        android:id="@+id/btnBind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bind to Service" >
    </Button>

    <Button
        android:id="@+id/btnUnbind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="Unbind from Service" >
    </Button>
</RelativeLayout>

<TextView
    android:id="@+id/textStatus"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Status Goes Here"
    android:textSize="24sp" />

<TextView
    android:id="@+id/textIntValue"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Integer Value Goes Here"
    android:textSize="24sp" />

<TextView
    android:id="@+id/textStrValue"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="String Value Goes Here"
    android:textSize="24sp" />

<RelativeLayout
    android:id="@+id/RelativeLayout03"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <Button
        android:id="@+id/btnUpby1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Increment by 1" >
    </Button>

    <Button
        android:id="@+id/btnUpby10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="Increment by 10" >
    </Button>
</RelativeLayout>

src\com.exampleservice\MainActivity.java:

package com.exampleservice;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    Button btnStart, btnStop, btnBind, btnUnbind, btnUpby1, btnUpby10;
    TextView textStatus, textIntValue, textStrValue;
    Messenger mService = null;
    boolean mIsBound;
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MyService.MSG_SET_INT_VALUE:
                textIntValue.setText("Int Message: " + msg.arg1);
                break;
            case MyService.MSG_SET_STRING_VALUE:
                String str1 = msg.getData().getString("str1");
                textStrValue.setText("Str Message: " + str1);
                break;
            default:
                super.handleMessage(msg);
            }
        }
    }
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = new Messenger(service);
            textStatus.setText("Attached.");
            try {
                Message msg = Message.obtain(null, MyService.MSG_REGISTER_CLIENT);
                msg.replyTo = mMessenger;
                mService.send(msg);
            }
            catch (RemoteException e) {
                // In this case the service has crashed before we could even do anything with it
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
            mService = null;
            textStatus.setText("Disconnected.");
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btnStart = (Button)findViewById(R.id.btnStart);
        btnStop = (Button)findViewById(R.id.btnStop);
        btnBind = (Button)findViewById(R.id.btnBind);
        btnUnbind = (Button)findViewById(R.id.btnUnbind);
        textStatus = (TextView)findViewById(R.id.textStatus);
        textIntValue = (TextView)findViewById(R.id.textIntValue);
        textStrValue = (TextView)findViewById(R.id.textStrValue);
        btnUpby1 = (Button)findViewById(R.id.btnUpby1);
        btnUpby10 = (Button)findViewById(R.id.btnUpby10);

        btnStart.setOnClickListener(btnStartListener);
        btnStop.setOnClickListener(btnStopListener);
        btnBind.setOnClickListener(btnBindListener);
        btnUnbind.setOnClickListener(btnUnbindListener);
        btnUpby1.setOnClickListener(btnUpby1Listener);
        btnUpby10.setOnClickListener(btnUpby10Listener);

        restoreMe(savedInstanceState);

        CheckIfServiceIsRunning();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("textStatus", textStatus.getText().toString());
        outState.putString("textIntValue", textIntValue.getText().toString());
        outState.putString("textStrValue", textStrValue.getText().toString());
    }
    private void restoreMe(Bundle state) {
        if (state!=null) {
            textStatus.setText(state.getString("textStatus"));
            textIntValue.setText(state.getString("textIntValue"));
            textStrValue.setText(state.getString("textStrValue"));
        }
    }
    private void CheckIfServiceIsRunning() {
        //If the service is running when the activity starts, we want to automatically bind to it.
        if (MyService.isRunning()) {
            doBindService();
        }
    }

    private OnClickListener btnStartListener = new OnClickListener() {
        public void onClick(View v){
            startService(new Intent(MainActivity.this, MyService.class));
        }
    };
    private OnClickListener btnStopListener = new OnClickListener() {
        public void onClick(View v){
            doUnbindService();
            stopService(new Intent(MainActivity.this, MyService.class));
        }
    };
    private OnClickListener btnBindListener = new OnClickListener() {
        public void onClick(View v){
            doBindService();
        }
    };
    private OnClickListener btnUnbindListener = new OnClickListener() {
        public void onClick(View v){
            doUnbindService();
        }
    };
    private OnClickListener btnUpby1Listener = new OnClickListener() {
        public void onClick(View v){
            sendMessageToService(1);
        }
    };
    private OnClickListener btnUpby10Listener = new OnClickListener() {
        public void onClick(View v){
            sendMessageToService(10);
        }
    };
    private void sendMessageToService(int intvaluetosend) {
        if (mIsBound) {
            if (mService != null) {
                try {
                    Message msg = Message.obtain(null, MyService.MSG_SET_INT_VALUE, intvaluetosend, 0);
                    msg.replyTo = mMessenger;
                    mService.send(msg);
                }
                catch (RemoteException e) {
                }
            }
        }
    }


    void doBindService() {
        bindService(new Intent(this, MyService.class), mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
        textStatus.setText("Binding.");
    }
    void doUnbindService() {
        if (mIsBound) {
            // If we have received the service, and hence registered with it, then now is the time to unregister.
            if (mService != null) {
                try {
                    Message msg = Message.obtain(null, MyService.MSG_UNREGISTER_CLIENT);
                    msg.replyTo = mMessenger;
                    mService.send(msg);
                }
                catch (RemoteException e) {
                    // There is nothing special we need to do if the service has crashed.
                }
            }
            // Detach our existing connection.
            unbindService(mConnection);
            mIsBound = false;
            textStatus.setText("Unbinding.");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            doUnbindService();
        }
        catch (Throwable t) {
            Log.e("MainActivity", "Failed to unbind from the service", t);
        }
    }
}

src\com.exampleservice\MyService.java:

package com.exampleservice;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

public class MyService extends Service {
    private NotificationManager nm;
    private Timer timer = new Timer();
    private int counter = 0, incrementby = 1;
    private static boolean isRunning = false;

    ArrayList<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
    int mValue = 0; // Holds last value set by a client.
    static final int MSG_REGISTER_CLIENT = 1;
    static final int MSG_UNREGISTER_CLIENT = 2;
    static final int MSG_SET_INT_VALUE = 3;
    static final int MSG_SET_STRING_VALUE = 4;
    final Messenger mMessenger = new Messenger(new IncomingHandler()); // Target we publish for clients to send messages to IncomingHandler.


    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
    class IncomingHandler extends Handler { // Handler of incoming messages from clients.
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_REGISTER_CLIENT:
                mClients.add(msg.replyTo);
                break;
            case MSG_UNREGISTER_CLIENT:
                mClients.remove(msg.replyTo);
                break;
            case MSG_SET_INT_VALUE:
                incrementby = msg.arg1;
                break;
            default:
                super.handleMessage(msg);
            }
        }
    }
    private void sendMessageToUI(int intvaluetosend) {
        for (int i=mClients.size()-1; i>=0; i--) {
            try {
                // Send data as an Integer
                mClients.get(i).send(Message.obtain(null, MSG_SET_INT_VALUE, intvaluetosend, 0));

                //Send data as a String
                Bundle b = new Bundle();
                b.putString("str1", "ab" + intvaluetosend + "cd");
                Message msg = Message.obtain(null, MSG_SET_STRING_VALUE);
                msg.setData(b);
                mClients.get(i).send(msg);

            }
            catch (RemoteException e) {
                // The client is dead. Remove it from the list; we are going through the list from back to front so this is safe to do inside the loop.
                mClients.remove(i);
            }
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("MyService", "Service Started.");
        showNotification();
        timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 0, 100L);
        isRunning = true;
    }
    private void showNotification() {
        nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.service_started);
        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.icon, text, System.currentTimeMillis());
        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.service_label), text, contentIntent);
        // Send the notification.
        // We use a layout id because it is a unique number.  We use it later to cancel.
        nm.notify(R.string.service_started, notification);
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("MyService", "Received start id " + startId + ": " + intent);
        return START_STICKY; // run until explicitly stopped.
    }

    public static boolean isRunning()
    {
        return isRunning;
    }


    private void onTimerTick() {
        Log.i("TimerTick", "Timer doing work." + counter);
        try {
            counter += incrementby;
            sendMessageToUI(counter);

        }
        catch (Throwable t) { //you should always ultimately catch all exceptions in timer tasks.
            Log.e("TimerTick", "Timer Tick Failed.", t);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (timer != null) {timer.cancel();}
        counter=0;
        nm.cancel(R.string.service_started); // Cancel the persistent notification.
        Log.i("MyService", "Service Stopped.");
        isRunning = false;
    }
}
很好的例子!另一个不错的功能:如果您将 android:process=:myservicename 属性添加到 manifest.xml 中服务的 service 标记,例如:<service android:name="sname" android:process=":myservicename" />,那么它将作为不同的进程运行您的服务 - 因此在不同的线程中。这意味着,服务完成的任何繁重计算/长时间请求都不会挂起您的 UI 线程。
我知道您为此付出了努力,但是将其放在 github 或类似的源代码共享站点上并在此处发布链接会更有意义。人们更容易以这种方式启动和运行它。
我不认为你关于并发修改的假设是准确的。
只有当您的服务可以被其他应用程序调用时,消息传递才是真正必要的。否则,您可能会坚持使用 Binder,它会返回对 Service 的引用并调用它的公共方法。
您应该先提出问题,然后自己创建答案,而不是在问题上回答问题。很好的例子;)

C
Christopher Orr

查看 LocalService example

您的 Service 将自身的一个实例返回给调用 onBind 的使用者。然后你就可以直接和服务交互了,比如给服务注册你自己的监听接口,这样你就可以得到回调了。


唯一的问题是它不会使用 Messenger,所以它不会回答这个伪问题。我使用了 LocalService,但我很高兴找到一个 Messenger/Handler 的示例。我不相信 LocalService 可以放在另一个进程中。
@Christoper-Orr:非常感谢您发布了 Android BroadcastReceiver 教程的链接。我使用 LocalBroadcastManager 在两个 Activity 实例之间不断交换数据。
LocalBroadcastManager 的问题在于它是非阻塞的,您必须等待结果。有时你想要立竿见影的效果。
你能帮我解决这个问题吗stackoverflow.com/questions/51508046/…
A
Afshin Ghazi

要将数据发送到服务,您可以使用:

Intent intent = new Intent(getApplicationContext(), YourService.class);
intent.putExtra("SomeData","ItValue");
startService(intent);

并在 onStartCommand() 服务后从意图中获取数据。

将数据或事件从服务发送到应用程序(用于一个或多个活动):

private void sendBroadcastMessage(String intentFilterName, int arg1, String extraKey) {
    Intent intent = new Intent(intentFilterName);
    if (arg1 != -1 && extraKey != null) {
        intent.putExtra(extraKey, arg1);
    }
    sendBroadcast(intent);
}

此方法是从您的服务调用的。您可以简单地为您的活动发送数据。

private void someTaskInYourService(){

    //For example you downloading from server 1000 files
    for(int i = 0; i < 1000; i++) {
        Thread.sleep(5000) // 5 seconds. Catch in try-catch block
        sendBroadCastMessage(Events.UPDATE_DOWNLOADING_PROGRESSBAR, i,0,"up_download_progress");
    }

要接收带有数据的事件,请在您的活动中创建并注册方法 registerBroadcastReceivers() :

private void registerBroadcastReceivers(){
    broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            int arg1 = intent.getIntExtra("up_download_progress",0);
            progressBar.setProgress(arg1);
        }
    };
    IntentFilter progressfilter = new IntentFilter(Events.UPDATE_DOWNLOADING_PROGRESS);
    registerReceiver(broadcastReceiver,progressfilter);

要发送更多数据,您可以修改方法 sendBroadcastMessage();。记住:你必须在 onResume() &在 onStop() 方法中取消注册!

更新

请不要在活动和服务之间使用我的通信类型。这是错误的方式。为了获得更好的体验,请使用特殊库,例如我们:

1) 来自 greenrobot 的 EventBus

2) 来自 Square Inc 的 Otto

PS我只在我的项目中使用来自greenrobot的EventBus,


我应该在哪里注册 onResume 和 onStop 中的接收者,我可能需要在活动中做些什么?
是的。要从服务接收事件,您必须在 onResume 中注册广播。请记住,您必须在 onStop 中取消注册广播。现在,我不建议使用我的方法。请使用特殊库与其他视图/活动/服务进行通信,例如 EventBus github.com/greenrobot/EventBus 或 Otto github.com/square/otto
你能帮我怎么用吗?我在服务通信中陷入了我的项目
谷歌是否建议反对这一点,或者你只是说它是“错误的”,因为你认为其他解决方案更好?
加一个用于提供指向 EventBusOtto 的链接。
P
Peter Mortensen

注意:您不需要检查您的服务是否正在运行,CheckIfServiceIsRunning(),因为如果它没有运行,bindService() 将启动它。

另外:如果您旋转电话,您不希望它再次bindService(),因为 onCreate() 将再次被呼叫。请务必定义 onConfigurationChanged() 以防止这种情况。


就我而言,我不需要一直运行该服务。如果活动开始时服务已经在运行,那么我想绑定到它。如果活动开始时服务没有运行,我想让服务停止。
我不确定这是不是真的,bindService 没有启动服务,你能指点文档吗?
developer.android.com/reference/android/app/Service.html 第一段指出Services can be started with Context.startService() and Context.bindService()
u
user1964369
Message msg = Message.obtain(null, 2, 0, 0);
                    Bundle bundle = new Bundle();
                    bundle.putString("url", url);
                    bundle.putString("names", names);
                    bundle.putString("captions",captions); 
                    msg.setData(bundle);

因此,您将其发送到服务。后领取。


A
Arunprasanth K V

一切都很好。activity/service 使用 Messenger 进行通信的好例子。

一条评论:方法 MyService.isRunning() 不是必需的。bindService() 可以执行任意次数。没有害处。

如果 MyService 在不同的进程中运行,则静态函数 MyService.isRunning() 将始终返回 false。所以不需要这个功能。


B
BaBL86

这就是我实现活动->服务通信的方式:在我的活动上

private static class MyResultReciever extends ResultReceiver {
     /**
     * Create a new ResultReceive to receive results.  Your
     * {@link #onReceiveResult} method will be called from the thread running
     * <var>handler</var> if given, or from an arbitrary thread if null.
     *
     * @param handler
     */
     public MyResultReciever(Handler handler) {
         super(handler);
     }

     @Override
     protected void onReceiveResult(int resultCode, Bundle resultData) {
         if (resultCode == 100) {
             //dostuff
         }
     }

然后我用它来启动我的服务

protected void onCreate(Bundle savedInstanceState) {
MyResultReciever resultReciever = new MyResultReciever(handler);
        service = new Intent(this, MyService.class);
        service.putExtra("receiver", resultReciever);
        startService(service);
}

在我的服务中,我有

public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null)
        resultReceiver = intent.getParcelableExtra("receiver");
    return Service.START_STICKY;
}

希望这可以帮助


Q
Quasaur

在我看来,您可以通过使用“实现 Handler.Callback”声明您的活动来节省一些内存


C
Community

很棒的教程,精彩的演示。整洁,简单,简短且非常易于解释。虽然,notification.setLatestEventInfo(this, getText(R.string.service_label), text, contentIntent); 方法已不复存在。正如 trante 所述here,好的方法是:

private static final int NOTIFICATION_ID = 45349;

private void showNotification() {
    NotificationCompat.Builder builder =
            new NotificationCompat.Builder(this)
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setContentTitle("My Notification Title")
                    .setContentText("Something interesting happened");

    Intent targetIntent = new Intent(this, MainActivity.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    builder.setContentIntent(contentIntent);
    _nManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    _nManager.notify(NOTIFICATION_ID, builder.build());
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (_timer != null) {_timer.cancel();}
    _counter=0;
    _nManager.cancel(NOTIFICATION_ID); // Cancel the persistent notification.
    Log.i("PlaybackService", "Service Stopped.");
    _isRunning = false;
}

检查自己,一切都像魅力一样(活动和服务名称可能与原始名称不同)。


K
Khemraj Sharma

我已经看到了所有的答案。我想告诉现在一天最强大的方式。这将使您在 Activity - Service - Dialog - Fragments(一切)之间进行通信。

EventBus

我在我的项目中使用的这个库具有与消息传递相关的强大功能。

EventBus 分 3 步

定义事件: public static class MessageEvent { /* 需要的附加字段 */ } 准备订阅者:

声明并注释您的订阅方法,可选择指定 thread mode

@Subscribe(threadMode = ThreadMode.MAIN) 
public void onMessageEvent(MessageEvent event) {/* Do something */};

注册和注销您的订阅者。例如在 Android 上,Activity 和 Fragment 通常应该根据它们的生命周期进行注册:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
}

发布事件:EventBus.getDefault().post(new MessageEvent());

只需在您的应用级别 gradle 中添加此依赖项

compile 'org.greenrobot:eventbus:3.1.1'

它没有 IPC,所以我们需要两种方式的信使。 AIDL 已弃用