【问题标题】:Accessing context from another thread从另一个线程访问上下文
【发布时间】:2014-03-16 17:12:08
【问题描述】:

我开始将一些 PC Java 应用程序迁移到 Android 环境,这是一个关于 Android 平台的完整新手。

当我尝试使用 Service 引用作为Toast 消息的上下文时,我发现了一个问题。

这是我的服务代码的相关部分:

public class ServicePFPE extends Service {

    Timer messageSimulator;
    TimerTask messagePoll;

    private class MessageReceptionTask extends TimerTask
    {
        public MessageReceptionTask(Context c) {
            context = c;
        }

        @Override
        public void run() {
            String shownText = "Message received!! on " + (new Date(System.currentTimeMillis())).toString();    
            //doToast(shownText, context);    //THIS LINE MAKES THE APP CRASH!
            System.out.println(shownText);    //But I can do this
        }    

        private Context context;
    }

    public ServicePFPE() {
        super();
        messageSimulator = new Timer();
        messagePoll = new MessageReceptionTask(this);
    }

    @Override    
    public IBinder onBind(Intent intent)    
    {    
            doToast("Service: onBind");    
            return null;    
    }    

    ...
    ...
    ...

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {    
        doToast("Service: onStartCommand");    
        messageSimulator.schedule(messagePoll, 5000, 5000);    
        return super.onStartCommand(intent, flags, startId);    
    }    

    ...
    ...
    ...

    private void doToast(String msg) { doToast(msg, this); }
    private void doToast(String msg, Context con) {
           Toast.makeText(con,msg,Toast.LENGTH_SHORT).show(); 
    }
}    

当计划任务运行到达 doToast 调用 Android 通知“不幸的是,myAPP 已停止”。

我认为这与我在不同线程中使用服务上下文的事实有关,但我不确定。

您能否确认是否是这种情况?从服务运行计时器并能够使用其上下文的正确方法是什么?如果这不可能,我能否获取该线程的上下文,以便生成 Toasts 用户消息。

【问题讨论】:

    标签: java android multithreading


    【解决方案1】:

    这取决于您真正需要什么,如果您打算显示简单的通知,也许您可​​以使用 Android 通知栏代替 toast(这是显示它们的标准方式)。例如,您可以使用:

      /**
         * Show a notification while this service is running.
         */
        private void showNotification() {
            // In this sample, we'll use the same text for the ticker and the expanded notification
            CharSequence text = getText(R.string.local_service_started);
    
            NotificationManager mNM;
            mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
            // Set the icon, scrolling text and timestamp
            Notification notification = new Notification(R.drawable.stat_sample, text,
                    System.currentTimeMillis());
    
            // The PendingIntent to launch our activity if the user selects this notification
            PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                    new Intent(this, LocalServiceActivities.Controller.class), 0);
    
            // Set the info for the views that show in the notification panel.
            notification.setLatestEventInfo(this, getText(R.string.local_service_label),
                           text, contentIntent);
    
            // Send the notification.
            mNM.notify(NOTIFICATION, notification);
        }
    

    但是,如果您只想敬酒,可以从服务中显示它们,您的问题是 timertask 正在与 UI 线程(运行服务的位置)不同的线程中执行。要将此代码“发布”到 UI 线程,您可以直接使用以下方法:

    Handler handler;
    
        @Override
        public void onCreate() {
            // Handler will get associated with the current thread, 
            // which is the main thread.
            handler = new Handler();
            super.onCreate();
        }
    
        private void runOnUiThread(Runnable runnable) {
            handler.post(runnable);
        }
    

    Source

    最后,如果您希望服务和活动之间完全交互,您有几种方法:

    1. 使用活页夹,对于简单的交流,这不是您所需要的。
    2. 使用信使进行更复杂的通信。
    3. 如果您只需要对话,您始终可以在对话模式下启动新活动。
    4. AIDL...

    关于 1 和 2 herehere 的文档

    1. 粘合剂: 它们允许您在应用程序中绑定不同的对象,让它们直接访问对象本身及其功能,例如来自 android doc:

      公共类 LocalService 扩展服务 { // 给客户的 Binder private final IBinder mBinder = new LocalBinder(); // 随机数生成器 private final Random mGenerator = new Random();

          /**
           * Class used for the client Binder.  Because we know this service always
           * runs in the same process as its clients, we don't need to deal with IPC.
           */
          public class LocalBinder extends Binder {
              LocalService getService() {
                  // Return this instance of LocalService so clients can call public methods
                  return LocalService.this;
              }
          }
      
          @Override
          public IBinder onBind(Intent intent) {
              return mBinder;
          }
      
          /** method for clients */
          public int getRandomNumber() {
            return mGenerator.nextInt(100);
          }
      }
      
      public class BindingActivity extends Activity {
          LocalService mService;
          boolean mBound = false;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.main);
          }
      
          @Override
          protected void onStart() {
              super.onStart();
              // Bind to LocalService
              Intent intent = new Intent(this, LocalService.class);
              bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
          }
      
          @Override
          protected void onStop() {
              super.onStop();
              // Unbind from the service
              if (mBound) {
                  unbindService(mConnection);
                  mBound = false;
              }
          }
      
          /** Called when a button is clicked (the button in the layout file attaches to
            * this method with the android:onClick attribute) */
          public void onButtonClick(View v) {
              if (mBound) {
                  // Call a method from the LocalService.
                  // However, if this call were something that might hang, then this request should
                  // occur in a separate thread to avoid slowing down the activity performance.
                  int num = mService.getRandomNumber();
                  Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
              }
          }
      
          /** Defines callbacks for service binding, passed to bindService() */
          private ServiceConnection mConnection = new ServiceConnection() {
      
              @Override
              public void onServiceConnected(ComponentName className,
                      IBinder service) {
                  // We've bound to LocalService, cast the IBinder and get LocalService instance
                  LocalBinder binder = (LocalBinder) service;
                  mService = binder.getService();
                  mBound = true;
              }
      
              @Override
              public void onServiceDisconnected(ComponentName arg0) {
                  mBound = false;
              }
          };
      }
      
    2. 信使: 更高级和更复杂,通过这种方式,您可以将消息从一个对象发送到另一个对象:

      公共类 MessengerService 扩展服务 { /** 命令服务显示消息 */ 静态最终 int MSG_SAY_HELLO = 1;

          /**
           * Handler of incoming messages from clients.
           */
          class IncomingHandler extends Handler {
              @Override
              public void handleMessage(Message msg) {
                  switch (msg.what) {
                      case MSG_SAY_HELLO:
                          Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                          break;
                      default:
                          super.handleMessage(msg);
                  }
              }
          }
      
          /**
           * Target we publish for clients to send messages to IncomingHandler.
           */
          final Messenger mMessenger = new Messenger(new IncomingHandler());
      
          /**
           * When binding to the service, we return an interface to our messenger
           * for sending messages to the service.
           */
          @Override
          public IBinder onBind(Intent intent) {
              Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
              return mMessenger.getBinder();
          }
      }
      
      
      
       public class ActivityMessenger extends Activity {
              /** Messenger for communicating with the service. */
              Messenger mService = null;
      
              /** Flag indicating whether we have called bind on the service. */
              boolean mBound;
      
              /**
               * Class for interacting with the main interface of the service.
               */
              private ServiceConnection mConnection = new ServiceConnection() {
                  public void onServiceConnected(ComponentName className, IBinder service) {
                      // This is called when the connection with the service has been
                      // established, giving us the object we can use to
                      // interact with the service.  We are communicating with the
                      // service using a Messenger, so here we get a client-side
                      // representation of that from the raw IBinder object.
                      mService = new Messenger(service);
                      mBound = true;
                  }
      
                  public void onServiceDisconnected(ComponentName className) {
                      // This is called when the connection with the service has been
                      // unexpectedly disconnected -- that is, its process crashed.
                      mService = null;
                      mBound = false;
                  }
              };
      
              public void sayHello(View v) {
                  if (!mBound) return;
                  // Create and send a message to the service, using a supported 'what' value
                  Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
                  try {
                      mService.send(msg);
                  } catch (RemoteException e) {
                      e.printStackTrace();
                  }
              }
      
              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.main);
              }
      
              @Override
              protected void onStart() {
                  super.onStart();
                  // Bind to the service
                  bindService(new Intent(this, MessengerService.class), mConnection,
                      Context.BIND_AUTO_CREATE);
              }
      
              @Override
              protected void onStop() {
                  super.onStop();
                  // Unbind from the service
                  if (mBound) {
                      unbindService(mConnection);
                      mBound = false;
                  }
              }
          }
      

    如果您想将活动显示为精美的对话框以显示更新,您可以使用具有此主题的常规活动:

    <activity android:theme="@android:style/Theme.Dialog" />
    

    【讨论】:

    • 天才!这个答案应该作为 Android 平台初学者了解活动和服务之间通信机制的教程。我最终决定使用通知和消息。
    【解决方案2】:

    任何与 UI 相关的代码都应使用 RunOnUiThread 方法在 UI 线程上运行。

    【讨论】:

    • 查看stackoverflow.com/questions/7942083/…了解如何操作。
    • 我想说你需要一个处理程序来做这件事。 @Tarik 是对的。
    • 我读到您可以使用 Binder 机制在 Service 和 Activity 之间传递消息,这就是我将要做的,以便让 Activity 执行 toast。但是,我最初认为我可以跟踪显示此祝酒词的预定代码中发生的事情。我知道我不能。
    【解决方案3】:

    你应该像这样设置一个全局上下文:

    public static Activity currentActivity=null;
    

    在运行您的主要活动或任何运行服务集上下文的活动之后:

    MainActivity.currentActivity = this;
    

    在 toast 之后使用这个上下文:

     Toast.makeText(MainActivity.currentActivity," text", Toast.LENGTH_LONG);
    

    希望能用完

    【讨论】:

    • 我不明白你的意思。你的意思是声明一个 ServicePFPE 属性,其中包含对引发启动服务的意图的活动的引用?
    • 不,只需像我的示例一样定义一个全局活动并在您的服务中设置值,然后您可以通过 currentActivity 的上下文从您的服务中调用 toast。去尝试一下。效果很好
    • 强烈建议不要使用对 Context 的静态引用,它会阻止 GC 回收此内存,并且任何上下文都会保留对所有应用程序资源的引用,因此如果您退出应用程序,它将保留对这个对象。 (更多信息在这里android-developers.blogspot.com.es/2009/01/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多