在活动中找不到方法

发布于 2024-08-31 21:03:48 字数 4159 浏览 10 评论 0原文

我从 Scala + Android 开始(并使用 sbt android 插件)。我正在尝试将按钮操作连接到没有实现 View.OnClickListener 的活动的按钮。

由于找不到该方法,运行时按钮单击失败。我正在处理的文档说我只需要声明一个公共 void 方法来获取操作上的视图,并在布局中使用该方法名称。

我做错了什么?

MainActivity.scala

package net.badgerhunt.hwa

import android.app.Activity
import android.os.Bundle
import android.widget.Button
import android.view.View
import java.util.Date

class MainActivity extends Activity {
  override def onCreate(savedInstanceState: Bundle) = {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main)
  }
  def calculate(button: View): Unit = println("calculating with %s ...".format(button))
}

res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/button"
    android:text=""
    android:onClick="calculate"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>

点击失败

D/AndroidRuntime(  362): Shutting down VM
W/dalvikvm(  362): threadid=3: thread exiting with uncaught exception (group=0x4001b188)
E/AndroidRuntime(  362): Uncaught handler: thread main exiting due to uncaught exception
E/AndroidRuntime(  362): java.lang.IllegalStateException: Could not find a method calculate(View) in the activity
E/AndroidRuntime(  362):    at android.view.View$1.onClick(View.java:2020)
E/AndroidRuntime(  362):    at android.view.View.performClick(View.java:2364)
E/AndroidRuntime(  362):    at android.view.View.onTouchEvent(View.java:4179)
E/AndroidRuntime(  362):    at android.widget.TextView.onTouchEvent(TextView.java:6540)
E/AndroidRuntime(  362):    at android.view.View.dispatchTouchEvent(View.java:3709)
E/AndroidRuntime(  362):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
E/AndroidRuntime(  362):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
E/AndroidRuntime(  362):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
E/AndroidRuntime(  362):    at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1659)
E/AndroidRuntime(  362):    at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1107)
E/AndroidRuntime(  362):    at android.app.Activity.dispatchTouchEvent(Activity.java:2061)
E/AndroidRuntime(  362):    at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1643)
E/AndroidRuntime(  362):    at android.view.ViewRoot.handleMessage(ViewRoot.java:1691)
E/AndroidRuntime(  362):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(  362):    at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(  362):    at android.app.ActivityThread.main(ActivityThread.java:4363)
E/AndroidRuntime(  362):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(  362):    at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(  362):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
E/AndroidRuntime(  362):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
E/AndroidRuntime(  362):    at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(  362): Caused by: java.lang.NoSuchMethodException: calculate
E/AndroidRuntime(  362):    at java.lang.ClassCache.findMethodByName(ClassCache.java:308)
E/AndroidRuntime(  362):    at java.lang.Class.getMethod(Class.java:1014)
E/AndroidRuntime(  362):    at android.view.View$1.onClick(View.java:2017)
E/AndroidRuntime(  362):    ... 20 more

更新

认为这可能是一个错误使用 sbt android 插件,我再次确保该方法在编译后存在。使用javap...

Compiled from "MainActivity.scala"
public class net.badgerhunt.hwa.MainActivity extends android.app.Activity implements scala.ScalaObject{
    public net.badgerhunt.hwa.MainActivity();
    public void calculate(android.view.View);
    public void onCreate(android.os.Bundle);
    public int $tag()       throws java.rmi.RemoteException;
}

I'm starting with Scala + Android (and using the sbt android plugin). I'm trying to wire a button action to a button without the activity implementing View.OnClickListener.

The button click fails at runtime because the method cannot be found. The document I'm working through says that I need only declare a public void method taking a View on the action, and use that method name in the layout.

What have I done wrong?

MainActivity.scala

package net.badgerhunt.hwa

import android.app.Activity
import android.os.Bundle
import android.widget.Button
import android.view.View
import java.util.Date

class MainActivity extends Activity {
  override def onCreate(savedInstanceState: Bundle) = {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main)
  }
  def calculate(button: View): Unit = println("calculating with %s ...".format(button))
}

res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/button"
    android:text=""
    android:onClick="calculate"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>

the failure onclick

D/AndroidRuntime(  362): Shutting down VM
W/dalvikvm(  362): threadid=3: thread exiting with uncaught exception (group=0x4001b188)
E/AndroidRuntime(  362): Uncaught handler: thread main exiting due to uncaught exception
E/AndroidRuntime(  362): java.lang.IllegalStateException: Could not find a method calculate(View) in the activity
E/AndroidRuntime(  362):    at android.view.View$1.onClick(View.java:2020)
E/AndroidRuntime(  362):    at android.view.View.performClick(View.java:2364)
E/AndroidRuntime(  362):    at android.view.View.onTouchEvent(View.java:4179)
E/AndroidRuntime(  362):    at android.widget.TextView.onTouchEvent(TextView.java:6540)
E/AndroidRuntime(  362):    at android.view.View.dispatchTouchEvent(View.java:3709)
E/AndroidRuntime(  362):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
E/AndroidRuntime(  362):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
E/AndroidRuntime(  362):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
E/AndroidRuntime(  362):    at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1659)
E/AndroidRuntime(  362):    at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1107)
E/AndroidRuntime(  362):    at android.app.Activity.dispatchTouchEvent(Activity.java:2061)
E/AndroidRuntime(  362):    at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1643)
E/AndroidRuntime(  362):    at android.view.ViewRoot.handleMessage(ViewRoot.java:1691)
E/AndroidRuntime(  362):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(  362):    at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(  362):    at android.app.ActivityThread.main(ActivityThread.java:4363)
E/AndroidRuntime(  362):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(  362):    at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(  362):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
E/AndroidRuntime(  362):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
E/AndroidRuntime(  362):    at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(  362): Caused by: java.lang.NoSuchMethodException: calculate
E/AndroidRuntime(  362):    at java.lang.ClassCache.findMethodByName(ClassCache.java:308)
E/AndroidRuntime(  362):    at java.lang.Class.getMethod(Class.java:1014)
E/AndroidRuntime(  362):    at android.view.View$1.onClick(View.java:2017)
E/AndroidRuntime(  362):    ... 20 more

UPDATE

Thinking that this may have been an error with the sbt android plugin I made doubly sure that the method was present after compilation. Using javap ...

Compiled from "MainActivity.scala"
public class net.badgerhunt.hwa.MainActivity extends android.app.Activity implements scala.ScalaObject{
    public net.badgerhunt.hwa.MainActivity();
    public void calculate(android.view.View);
    public void onCreate(android.os.Bundle);
    public int $tag()       throws java.rmi.RemoteException;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

ζ澈沫 2024-09-07 21:03:48

sbt android 插件包含一个 ProGuard 任务,可以删除所有未使用的代码。非常酷的东西,有助于真正缩小生成的 .apk 文件,但不幸的是,回调方法通常不会在代码中引用,因此默认情况下,ProGuard 会将它们丢弃。
要亲自查看它,请尝试在 MainActivity 类上使用 javap,但将类路径设置为 target/your_scala_version/classes.min.jar。
你需要告诉 proguard 明确保留什么。 sbt android 插件的默认设置中已经有一组 -keep 选项,但这是特定于您的项目的,因此您必须在 project/build/YourProjectName.scala 中编辑项目定义。查看 sbt android 插件的代码并了解 proguardTask 的定义。您必须覆盖它并添加额外的 -keep 选项。这就是我所做的:

import sbt._
import java.io._
import proguard.{Configuration=>ProGuardConfiguration, ProGuard, ConfigurationParser}
import sbt._
import Process._

trait Defaults {
  def androidPlatformName = "android-1.6"
}
class TestAndro2(info: ProjectInfo) extends ParentProject(info) {
  override def shouldCheckOutputDirectories = false
  override def updateAction = task { None }

  lazy val main  = project(".", "testAndro2", new MainProject(_))

  class MainProject(info: ProjectInfo) extends AndroidProject(info) with Defaults {
    val scalatest = "org.scalatest" % "scalatest" % "1.0" % "test"
    override def proguardTask = task { 
      val args = "-injars" ::  mainCompilePath.absolutePath+File.pathSeparator+
      scalaLibraryJar.getAbsolutePath+"(!META-INF/MANIFEST.MF,!library.properties)"+
      (if (!proguardInJars.getPaths.isEmpty) File.pathSeparator+proguardInJars.getPaths.map(_+"(!META-INF/MANIFEST.MF)").mkString(File.pathSeparator) else "") ::                             
        "-outjars" :: classesMinJarPath.absolutePath ::
        "-libraryjars" :: libraryJarPath.getPaths.mkString(File.pathSeparator) :: 
        "-dontwarn" :: "-dontoptimize" :: "-dontobfuscate" :: 
        "-dontwarn" :: "-dontoptimize" :: "-dontobfuscate" :: "-printseeds" ::
        """-keep public class com.test.android.MainActivity {
          public void calculate(android.view.View);
        }""" ::
        "-keep public class * extends android.app.Activity" ::
        "-keep public class * extends android.app.Service" ::
        "-keep public class * extends android.appwidget.AppWidgetProvider" ::
        "-keep public class * implements junit.framework.Test { public void test*(); }" :: proguardOption :: Nil

        val config = new ProGuardConfiguration
        new ConfigurationParser(args.toArray[String], info.projectPath.asFile).parse(config)    
        new ProGuard(config).execute
        None
      }
    }
  }

本质上,我添加了 -printseeds 和一个 -keep 选项来保留 MainActivity 的calculate() 方法。 -printseeds 非常适合调试,因为它告诉 proguard 打印已保留的类和方法的名称。
ProGuard 有大量的配置选项,您在构建项目时需要密切关注它们,因为在很多不明确的情况下,ProGuard 默认情况下不会做正确的事情。

The sbt android plugin includes a ProGuard task that strips out all unused code. Very cool stuff and helps to really slim down the resulting .apk file but unfortunately callback methods aren't usually referenced in your code so, by defalt, ProGuard will throw them out.
To see it yourself try using javap on the MainActivity class but set the classpath to target/your_scala_version/classes.min.jar.
You need to tell proguard what to explicitly keep. There is already a set of -keep options in the default setup of sbt android plugin but this is specific to your project so you will have to edit your project definition in project/build/YourProjectName.scala. Look at the code of sbt android plugin and lok for proguardTask definition. You will have to override that and add your additional -keep options. This is what I did:

import sbt._
import java.io._
import proguard.{Configuration=>ProGuardConfiguration, ProGuard, ConfigurationParser}
import sbt._
import Process._

trait Defaults {
  def androidPlatformName = "android-1.6"
}
class TestAndro2(info: ProjectInfo) extends ParentProject(info) {
  override def shouldCheckOutputDirectories = false
  override def updateAction = task { None }

  lazy val main  = project(".", "testAndro2", new MainProject(_))

  class MainProject(info: ProjectInfo) extends AndroidProject(info) with Defaults {
    val scalatest = "org.scalatest" % "scalatest" % "1.0" % "test"
    override def proguardTask = task { 
      val args = "-injars" ::  mainCompilePath.absolutePath+File.pathSeparator+
      scalaLibraryJar.getAbsolutePath+"(!META-INF/MANIFEST.MF,!library.properties)"+
      (if (!proguardInJars.getPaths.isEmpty) File.pathSeparator+proguardInJars.getPaths.map(_+"(!META-INF/MANIFEST.MF)").mkString(File.pathSeparator) else "") ::                             
        "-outjars" :: classesMinJarPath.absolutePath ::
        "-libraryjars" :: libraryJarPath.getPaths.mkString(File.pathSeparator) :: 
        "-dontwarn" :: "-dontoptimize" :: "-dontobfuscate" :: 
        "-dontwarn" :: "-dontoptimize" :: "-dontobfuscate" :: "-printseeds" ::
        """-keep public class com.test.android.MainActivity {
          public void calculate(android.view.View);
        }""" ::
        "-keep public class * extends android.app.Activity" ::
        "-keep public class * extends android.app.Service" ::
        "-keep public class * extends android.appwidget.AppWidgetProvider" ::
        "-keep public class * implements junit.framework.Test { public void test*(); }" :: proguardOption :: Nil

        val config = new ProGuardConfiguration
        new ConfigurationParser(args.toArray[String], info.projectPath.asFile).parse(config)    
        new ProGuard(config).execute
        None
      }
    }
  }

Essentially, I added the -printseeds and one -keep option to keep the calculate() method of the MainActivity. -printseeds is good for debugging as it tells proguard to print the names of classes and methods that have been kept.
ProGuard has a huge set of configuration options and you will need to keep an eye on them when building your project as there are lots of ambiguous situations where ProGuard won't do the right thing by default.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文