Flutter Android平台的EventChannel在按返回按钮销毁应用程序时未停止。

huangapple go评论81阅读模式
英文:

Flutter Android Platform EventChannel not stopping when back button press destroy application

问题

我正在尝试创建一个Flutter应用程序,该应用程序使用我正在创建的本机Android插件的后台服务:一个简单的计数器应用程序,每隔5秒通过EventChannel发送计数值。

当我按下返回按钮并销毁应用程序时,服务仍然在运行(这是我想要的)。但是当我再次打开应用程序时,事件流不再将数据发送到新的Flutter视图。
我认为事件通道插件没有停止。

英文:

I am trying to create a flutter application which uses the background service from native android plugin I am creating: a simple counter application which sends count value after every 5 seconds through EventChannel.

When I press the back button and the application is destroyed, the service is still running (that's what I want to do). But when I open the application again, the event sink is no longer sending data to new flutter view.
I think the event channel plugin is not stopping.

答案1

得分: 2

当我们按下返回按钮时,我们的应用程序会被销毁。因此,事件通道(位于我们的本机代码中)无法将数据发送到我们的Flutter端。

您可以在控制台中看到以下消息:
尝试将平台消息发送到Flutter,但FlutterJNI已从本机C++分离。无法发送。通道:com.example.timestamp_saver 响应ID:0
这里的com.example.timestamp_saver是事件通道的名称。

因此,要在按下返回按钮后再次打开应用程序时开始监听事件,您需要在应用程序状态处于“resumed”状态下或某个按钮按下时重新开始监听该通道。

要根据应用程序状态执行此操作,请按照以下步骤进行操作 -

  1. 首先,在您的Stateful widget中实现WidgetsBindingObserver

  2. 然后在initState()方法和dispose()方法内部添加以下内容:

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
  WidgetsBinding.instance.removeObserver(this);
  super.dispose();
}
  1. 现在覆盖didChangeAppLifecycleState(AppLifecycleState state)并获取Flutter应用程序的当前状态。 或者 您还可以在某个刷新按钮按下时开始监听Event Sink。
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  print('state = $state');
  if (state == AppLifecycleState.resumed) {
    // 重新开始监听Event Sink
    listenData();
  }
}

以下是listenData()方法:

void listenData() async {
  if (Platform.isAndroid) {
    var eventChannel = EventChannel("com.example.timestamp_saver");
    eventChannel.receiveBroadcastStream("Just Listen").listen((event) async {
      debugPrint("Inside Listen Data");
      debugPrint(event);
    });
  }
}
  1. 这是我的示例代码,我每5秒更新一次时间戳列表。当用户按下返回按钮时,监听器将更新为EventSink发出的数据,因此在这种情况下,我将数据保存到数据库中,当用户再次打开应用程序时,我将开始重新监听相同的通道。

以下是main.dart:

// 请注意,此处省略了其他部分,仅显示关键部分
void startServiceAndReceiveUpdatea() async {
  if (Platform.isAndroid) {
    var eventChannel = EventChannel("com.example.timestamp_saver");
    eventChannel.receiveBroadcastStream("startService").listen((event) async {
      debugPrint("Listner called in dart");
      debugPrint(event);
      TimeStampModel timeStamp = TimeStampModel(id: null, timestamp: event);
      setState(() {});
    });
  }
}

void listenData() async {
  if (Platform.isAndroid) {
    var eventChannel = EventChannel("com.example.timestamp_saver");
    eventChannel.receiveBroadcastStream("Just Listen").listen((event) async {
      debugPrint("Inside Listen Data");
      debugPrint(event);
      TimeStampModel timeStamp = TimeStampModel(id: null, timestamp: event);
      setState(() {});
    });
  }
}

这应该能帮助您处理在按下返回按钮后重新监听事件通道的问题。

英文:

When we press the back button our application gets destroyed. So EventSink (Which resides in our Native Code) can't send data to our flutter side.

You can see this message in your console:
Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: com.example.timestamp_saver Response ID: 0
Here com.example.timestamp_saver is Event Channel name.

So to start listening to the events again when you open the app again after pressing back button,you need to start listening to that channel again and you can do this thing when your app state in "resumed" OR on some button pressed.

To do this according to the app state,follow the below steps -

  1. So first implement your Stateful widget with WidgetsBindingObserver

  2. then inside initState() method and dispose()

      @override
      void initState() {
       super.initState();
       WidgetsBinding.instance.addObserver(this);
       }
    
      @override
      void dispose() {
      WidgetsBinding.instance.removeObserver(this);
      super.dispose();
      }
    
  3. Now override didChangeAppLifecycleState(AppLifecycleState state) and get the current state of your application in flutter.
    OR
    You can start listening to Event Sink on some Refresh Buttom pressed also

    @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
     print('state = $state');
     if(state == AppLifecycleState.resumed)
       {
         //start listening to the Event Sink again
         listenData();
       }
     }
    

Here is the listenData() method -

void listenData() async {
 if(Platform.isAndroid){
        var eventChannel = EventChannel("com.example.timestamp_saver");
        eventChannel.receiveBroadcastStream("Just Listen").listen((event) async {
         debugPrint("Inside Listen Data");
         debugPrint(event);
      });
     }
   } 
  1. Here is my example code where I'm updating the list with timestamps in every 5 seconds.When user presses back button in that case the listener will be updated with the data emitted by EventSink so in this case I'm saving data to Database and when user will open the app again, there I'm staring to listen to the same Channel again.

main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:timestamp_saver/db/db_helper.dart';
import 'package:timestamp_saver/db/timestamp_model.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

 class MyHomePage extends StatefulWidget {
   @override
   _MyHomePageState createState() => _MyHomePageState();
 }

 class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
   DBHelper _dbHelper;
   List<TimeStampModel> timeStampList;

   @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _dbHelper = DBHelper();
  }

   @override
   void dispose() {
     WidgetsBinding.instance.removeObserver(this);
     super.dispose();
   }
 

   @override
   Widget build(BuildContext context) {
     return Scaffold(
         appBar: AppBar(),
         body: Container(
           child: Column(
             children: [
               RaisedButton(
                 onPressed: (){startServiceAndReceiveUpdatea();},
                 child: Text("Start"),
               ),
               SizedBox(height: 20,),
               RaisedButton(
                 onPressed: (){listenData();},
                 child: Text("Listen"),
               ),
               Expanded(
                   child: FutureBuilder<List<TimeStampModel>>(
                       future: _dbHelper.getTimeStampModels(),
                       builder: (context, snapshot) {
                          if(snapshot.hasData)
                            {
                              timeStampList = snapshot.data;
                              return ListView.builder(
                                   itemCount: timeStampList.length,
                                  itemBuilder: (context, index) => ListTile(
                                    title: Text(timeStampList[index].timestamp),
                                  ),
                              );
                            }else if(snapshot.hasError)
                            return Center(child: Text("Error"),);
                          else
                            return Center(child: CircularProgressIndicator(),);
                       },
                   )
               )
             ],
           ),
         )
     );
   }

  

   void startServiceAndReceiveUpdatea() async
   {
     if(Platform.isAndroid){
       var eventChannel = EventChannel("com.example.timestamp_saver");
       eventChannel.receiveBroadcastStream("startService").listen((event) async {
         debugPrint("Listner called in dart");
         debugPrint(event);
         TimeStampModel timeStamp = TimeStampModel(id: null,timestamp: event);
         setState(() {

         });
    });
     }
   }

   void listenData() async
   {
     if(Platform.isAndroid){
       var eventChannel = EventChannel("com.example.timestamp_saver");
       eventChannel.receiveBroadcastStream("Just Listen").listen((event) async {
         debugPrint("Inside Listen Data");
         debugPrint(event);
         TimeStampModel timeStamp = TimeStampModel(id: null,timestamp: event);
         setState(() {

         });

       });
     }
   }


}

MainActivity.java

package com.example.timestamp_saver;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import java.nio.ByteBuffer;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

    private Intent forService;
    static EventChannel.EventSink events;
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        forService = new Intent(MainActivity.this,MyService.class);
 new EventChannel(flutterEngine.getDartExecutor(),"com.example.timestamp_saver").setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                MainActivity.events = events;
                System.out.println("On Listen in Activity called");
                     if(arguments.equals("startService"))
                     {
                         System.out.println("if");
                         startService();
                    }else if(arguments.equals("Just Listen")){
                         System.out.println("Just Listen");
                     }
            }

            @Override
            public void onCancel(Object arguments) {

            }
        });

    }

    private void startService(){
       if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            startForegroundService(forService);
        } else {
            startService(forService);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

MyService.java

package com.example.timestamp_saver;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.lifecycle.LifecycleService;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import io.flutter.plugin.common.EventChannel;

public class MyService extends LifecycleService {

    MutableLiveData<String> stringMutableLiveData = new MutableLiveData<>();
    int timerCount = 0;
    private PowerManager.WakeLock wl;
    private DBManager dbManager;
    @Override
    public void onCreate() {
        super.onCreate();
        dbManager = new DBManager(this);
        dbManager.open();
        //Creating a partial wake lock,Because if we don't create wake lock then CPU Will stop working when
        //screen will be locked,which will cause timer to work inconsistently.

        //This solution can be modified more.For example Acquire the Wake Lock when user locks the screen and release
        //it when phone gets unlock.This can be achieved using Brodcast Receiver.
        //Because Wake Lock consumes much power,so use it properly.
        PowerManager pm = (PowerManager)  getSystemService(Context.POWER_SERVICE);
        wl =  pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"a:waketag");
        wl.acquire();
        //stringMutableLiveData = new MutableLiveData<>();
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"messages")
                    .setContentText("This is running in Background")
                    .setContentTitle("Flutter Background")
                    .setSmallIcon(R.drawable.launch_background);

            startForeground(101,builder.build());
        }

        timerLogic();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    void timerLogic()
    {
        stringMutableLiveData.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                System.out.println("On Changed");
                if(MainActivity.events != null) {
                    dbManager.insert(s);
                    MainActivity.events.success(s);
                }
                else
                    System.out.println("Event is null");
            }
        });
         

        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                timerCount++;
                DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                Calendar cal = Calendar.getInstance();
                String timerString = dateFormat.format(cal.getTime());
                System.out.println(timerString+": "+timerCount);
                stringMutableLiveData.postValue(timerString);

            }
        },1000,5000);
    }

    @Override
    public void onDestroy() {
        //Remember to relese wake lock
        wl.release();
        super.onDestroy();

    }
}

huangapple
  • 本文由 发表于 2020年1月6日 19:56:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/59611686.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定