MethodChannel: التنفيذ على Android بـ Kotlin
MethodChannel: التنفيذ على Android بـ Kotlin
عندما يحتاج Flutter إلى استدعاء وظيفة Android الأصلية — مثل قراءة مستوى بطارية الجهاز، أو الوصول المباشر إلى كاميرا الجهاز، أو استدعاء SDK منصة معينة — فإنك تُسجِّل معالج MethodChannel على الجانب الأندرويدي. يتناول هذا الدرس كيفية تنفيذ ذلك المعالج داخل FlutterActivity (أو FlutterPlugin) باستخدام Kotlin، وربطه بشكل صحيح، وإرجاع النتائج أو الأخطاء المنظَّمة إلى Dart.
أين يوجد الكود الأندرويدي
في مشروع Flutter قياسي، يقع كود المضيف الأندرويدي في android/app/src/main/kotlin/…/MainActivity.kt. نقطة الدخول هي فئة ترث من FlutterActivity. تُعيد تعريف configureFlutterEngine للحصول على الوصول إلى FlutterEngine وتسجيل قنواتك هناك.
configureFlutterEngine هو الخطاف الصحيح في دورة الحياة لتسجيل القنوات. تجنَّب فعل ذلك في onCreate لأن المحرك قد لا يكون مُهيَّأً بالكامل في تلك المرحلة.تسجيل MethodChannel في MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.myapp/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error(
"UNAVAILABLE",
"Battery level not available.",
null
)
}
}
else -> result.notImplemented()
}
}
}
}
اللامبدا الخاص بـ MethodCallHandler
يستقبل اللامبدا الممرَّر إلى setMethodCallHandler معاملَين في كل مرة تستدعي فيها Dart القناة:
call: MethodCall— يحتوي علىcall.method(اسم السلسلة النصية) وcall.arguments(أي قيمة مُرسَلة من Dart، من النوعAny?).result: MethodChannel.Result— كائن رد النداء الذي تستخدمه للرد مرة واحدة بالضبط:result.success(value)، أوresult.error(code, message, details)، أوresult.notImplemented().
success أو error أو notImplemented لكل استدعاء. عدم استدعاء أي منها يُبقي Future الخاص بـ Dart معلَّقاً إلى الأبد؛ واستدعاء أكثر من واحدة يُلقي استثناءً في وقت التشغيل.استخراج المعاملات بأمان
يمكن لـ Dart تمرير المعاملات كـ Map أو List أو قيمة بدائية. على جانب Kotlin، حوِّل call.arguments إلى النوع المتوقع. استخدم المساعد المكتوب call.argument<T>(key) لمعاملات الخريطة لتجنُّب التحويلات غير الآمنة.
معالجة المعاملات وإرجاع نتيجة
"greet" -> {
// أرسلت Dart: {'name': 'Flutter'}
val name: String? = call.argument<String>("name")
if (name == null) {
result.error("MISSING_ARG", "Argument 'name' is required.", null)
return@setMethodCallHandler
}
result.success("Hello from Kotlin, $name!")
}
"multiply" -> {
val a: Int? = call.argument<Int>("a")
val b: Int? = call.argument<Int>("b")
if (a == null || b == null) {
result.error("MISSING_ARG", "Both 'a' and 'b' are required.", null)
return@setMethodCallHandler
}
result.success(a * b)
}
التنفيذ كـ FlutterPlugin
للحصول على كود أصلي قابل لإعادة الاستخدام (مثل مكتبة موزَّعة عبر pub.dev)، نفِّذ واجهة FlutterPlugin بدلاً من تضمين المنطق داخل MainActivity. هذا يفصل تسجيل القناة عن النشاط المضيف ويُمكِّن البرنامج المساعد من التسجيل التلقائي.
- نفِّذ
FlutterPluginوأعِد تعريفonAttachedToEngineوonDetachedFromEngine. - سجِّل القناة في
onAttachedToEngineباستخدامFlutterPluginBindingالمُقدَّم. - أَلغِ التسجيل (اضبط المعالج على
null) فيonDetachedFromEngineلمنع تسريب الذاكرة.
نمط تسجيل FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
class BatteryPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(
binding.binaryMessenger,
"com.example.myapp/battery"
)
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"getBatteryLevel" -> result.success(getNativeBatteryLevel())
else -> result.notImplemented()
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) // تجنُّب تسريب الذاكرة
}
private fun getNativeBatteryLevel(): Int {
// ... تنفيذ المنصة
return 87
}
}
اعتبارات الخيوط (Threading)
بشكل افتراضي، يُستدعى MethodCallHandler على الخيط الرئيسي (خيط واجهة المستخدم). إذا كان عملك الأصلي طويل الأمد (إدخال/إخراج ملفات، شبكة، Bluetooth)، فانقله إلى خيط خلفي ثم استدعِ result.success() مجدداً على الخيط الرئيسي باستخدام Handler(Looper.getMainLooper()).post { … }، أو استخدم كوروتينات Kotlin مع Dispatchers.Main للرد.
notImplemented. عرِّف الاسم في ثابت مشترك أو اجعله مرئياً في كلا الملفَّين لسهولة المقارنة.ملخص
لتنفيذ معالج MethodChannel على Android: أعِد تعريف configureFlutterEngine في MainActivity (أو نفِّذ FlutterPlugin للإضافات القابلة لإعادة الاستخدام)؛ أنشئ MethodChannel بالمُرسِل الثنائي واسم قناة مطابق؛ زوِّد اللامبدا الخاص بـ setMethodCallHandler الذي يُوزِّع على call.method؛ ارد بواحدة بالضبط من result.success أو result.error أو result.notImplemented؛ ونظِّف بضبط المعالج على null عند فصل المحرك.