Não consigo encontrar o método na atividade
Pergunta
Estou começando com o Scala + Android (e usando o plug -in Android SBT). Estou tentando conectar uma ação de botão para um botão sem a atividade implementando o View.ClickListener.
O clique do botão falha no tempo de execução porque o método não pode ser encontrado. O documento em que estou trabalhando diz que preciso declarar apenas um método de vazio público, observando a ação e use esse nome de método no layout.
O que eu fiz errado?
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"/>
o fracasso 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
ATUALIZAR
Pensando que isso pode ter sido um erro com o plug -in Android do SBT, fiz duplamente certeza de que o método estava presente após a compilação. Usando 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;
}
Solução
O plug -in Android do SBT inclui uma tarefa de proguard que retira todo o código não utilizado. Coisas muito legais e ajuda a realmente diminuir o arquivo .apk resultante, mas, infelizmente, os métodos de retorno de chamada geralmente não são referenciados em seu código, por isso, por Defalt, o ProGuard os jogará fora. Para ver você mesmo, tente usar o Javap na classe MainActivity, mas defina o ClassPath como Target/Your_Scala_version/classes.min.jar. Você precisa dizer a Proguard o que manter explicitamente. Já existe um conjunto de opções de manutenção na configuração padrão do plug -in Android SBT, mas isso é específico para o seu projeto, para que você precise editar sua definição de projeto em projeto/build/yourprojectName.scala. Veja o código do SBT Android Plugin e LOK para obter a definição do ProGuardTask. Você precisará substituir isso e adicionar suas opções adicionais de manutenção. Isso é o que eu fiz:
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
, Eu adicionei a opção -rintseeds e uma opção para manter o método calcular () da MainActivity. -Printseeds é bom para a depuração, pois diz a Proguard imprimir os nomes de classes e métodos que foram mantidos. O ProGuard tem um enorme conjunto de opções de configuração e você precisará ficar de olho nelas ao criar seu projeto, pois há muitas situações ambíguas em que o ProGuard não fará a coisa certa por padrão.