从Service类调用Activity类的方法

58
我在SO上看到许多关于这个问题的帖子,但是我无法找到最简单的方法来从服务类中调用活动方法。广播接收器是唯一的选择吗?没有简单的方法吗?我只需要在服务类准备好媒体播放器后调用活动类中的以下方法。
活动类:
    public void updateProgress() {
    // set Progress bar values
    songProgressBar.setProgress(0);
    songProgressBar.setMax(100);
    // Updating progress bar
    updateProgressBar();
}

服务类:

   @Override
public IBinder onBind(Intent intent) {
    Log.d(this.getClass().getName(), "BIND");
    return musicBind;
}

@Override
public boolean onUnbind(Intent intent) {
    return false;
}
    @Override
public void onPrepared(MediaPlayer mp) {
    try {
        mp.start();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    }

           // updateProgress();// Need to call the Activity method here 
  }
7个回答

118

定义一个接口,您的服务将使用它来通信事件:

public interface ServiceCallbacks {
    void doSomething();
} 

编写你的Service类。你的Activity将绑定到该服务,因此请遵循此处示例所示的方式。另外,我们会添加一个方法来设置ServiceCallbacks

public class MyService extends Service {
    // Binder given to clients
    private final IBinder binder = new LocalBinder();
    // Registered callbacks
    private ServiceCallbacks serviceCallbacks;


    // Class used for the client Binder.
    public class LocalBinder extends Binder {
        MyService getService() {
            // Return this instance of MyService so clients can call public methods
            return MyService.this;
        }
    }

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

    public void setCallbacks(ServiceCallbacks callbacks) {
        serviceCallbacks = callbacks;
    }
}

按照同样的指南编写您的Activity类,但还要使其实现您的ServiceCallbacks接口。当您从服务中绑定/解除绑定时,通过在服务上调用setCallbacks来注册/取消注册。

public class MyActivity extends Activity implements ServiceCallbacks {
    private MyService myService;
    private boolean bound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(...);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // bind to Service
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from service
        if (bound) {
            myService.setCallbacks(null); // unregister
            unbindService(serviceConnection);
            bound = false;
        }
    }

    /** Callbacks for service binding, passed to bindService() */
    private ServiceConnection serviceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            // cast the IBinder and get MyService instance
            LocalBinder binder = (LocalBinder) service;
            myService = binder.getService();
            bound = true;
            myService.setCallbacks(MyActivity.this); // register
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            bound = false;
        }
    };

    /* Defined by ServiceCallbacks interface */
    @Override
    public void doSomething() {
        ...
    }
}

现在,当您的服务需要与活动进行通信时,只需从先前的接口方法中调用其中之一即可。在您的服务内部:
if (serviceCallbacks != null) { 
    serviceCallbacks.doSomething();
}

1
除了onServiceConnected下的这一行代码:service.setCallbacks(this);,其他都看起来没问题。它抱怨想要一个ServiceCallbacks,但是被传递了一个匿名的ServiceConnection - RevMen
应该是MyActivity.this。我会更正这篇文章。 - Karakuri
在 "binder.getService();" 处,我收到了错误提示:"无法将 MyService 转换为 IBinder"。 - Vlad.mir
1
@Vlad,看起来我在类成员和方法参数中使用了相同的标识符。我已经进行了必要的更正。 - Karakuri
1
它报错了:'04-17 03:03:46.715: E/AndroidRuntime(25591): java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.example.create.main.RePublishService$LocalBinder';我需要我的服务在一个单独的进程中运行。 - Zen
显示剩余5条评论

10

使用广播接收器和服务类一起更新视图。例如:

  1. 在我的活动类中

    public class ServiceDemoActivity extends Activity {
    
        Intent intent;
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            final TextView notification = (TextView) findViewById(R.id.notification);
            if (CheckIfServiceIsRunning()) {
    
            } else {
                startService(new Intent(this, MyService.class));
            }
        }
    
        private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updateDate(intent);
            }
        };
    
        private void updateDate(Intent intent) {
            String time = intent.getStringExtra("time");
            Toast.makeText(getApplicationContext(), "Yea!!! Service called", Toast.LENGTH_SHORT).show();
            TextView date = (TextView) findViewById(R.id.date);
            date.setText(time);
        }
    
        @Override
        public void onResume() {
            super.onResume();
            registerReceiver(broadcastReceiver, new IntentFilter(
                    MyService.BROADCAST_ACTION));
        }
    }
    

    在我的服务类中,我会在一段时间间隔后调用我的更新UI函数,以更新我的用户界面。

    public class MyService extends Service {    
    
            public static final String
            BROADCAST_ACTION = "com.mukesh.service";    
            private final Handler handler = new Handler();
    
    
            @Override
            public void onCreate() {
    
                intent = new Intent(BROADCAST_ACTION);
            }
    
            @Override   
            public void onDestroy() {
    
                stopService(intent);
            }
    
            @Override
            public void onStart(Intent intent, int startid) {
                int i = 0;
                while (i <= 2) {
                    if (i > 1) {
                        i++;
                        this.onDestroy();
                    } else {
                        counter = i;
                        i++;
                        handler.removeCallbacks(sendUpdatesToUI);
                        handler.postDelayed(sendUpdatesToUI, 1 * 1000); // 1 sec
                    }
                }       
            }
    
            private Runnable sendUpdatesToUI = new Runnable() {
                public void run() {
                    DisplayLoggingInfo();           
                            handler.postDelayed(this, 7 * 1000); // 7 sec
                }
            };
    
            private void DisplayLoggingInfo() {  
                intent.putExtra("time", new Date().toLocaleString());
                intent.putExtra("counter", String.valueOf(counter));
                sendBroadcast(intent);
                stopService(intent);
            }
     }
    

完整的代码请查看此链接


2

我创建了一个名为Delegate(名称不是特别重要,你可以叫它John)的通用类,并将MainActivity类作为静态字段传递给它。然后我可以从服务中访问它,因为它现在是全局的。我不确定它是否具有成本效益,但对我来说很简单地解决了问题。

我的服务:

package com.some.package;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;

public class FirebaseInstanceIDService extends FirebaseInstanceIdService {

    @Override
    public void onTokenRefresh() {
        String token = FirebaseInstanceId.getInstance().getToken();
        Delegate.theMainActivity.onDeviceTokenChange(token);
    }
}

委托类:

package com.some.package;

public class Delegate {
    static MainActivity theMainActivity;
}

我在MainActivity中做了什么:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Delegate.theMainActivity = this;

    //rest of the code...
}

public void onDeviceTokenChange(String token){
    Log.e("updated token:", token);
}

11
那是一种内存泄漏。 - Tim

0

您可以通过实现自己的监听器来从服务中调用活动方法,例如:

https://dev59.com/mXNA5IYBdhLWcg3wWseK#18585247

您可以考虑像这样在runOnUiThread中运行您的活动方法:

    // method will be called from service
    override fun callback(activity: Activity, result: String) {
        runOnUiThread{
            Toast.makeText(activity, result, Toast.LENGTH_LONG).show()
        }
    }

0

你可以从你的服务中调用

 getContentResolver().notifyChange(uri, null);

在你的活动中设置一个

getContentResolver().registerContentObserver(uri, false, new ContentObserver(getHandler())
{
    public void onChange(boolean selfChange)
    {
        updateProgress()
    }
};

onChange方法将在UI线程上调用


uri的值应该是什么?我认为你的代码无法编译。 - likejudo

0

我从Activity直接调用我的Service方法。为什么不应该这样做? - John Glen

0

我更喜欢使用EventBus提供的一些非常简单和更清晰的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接