scala on android

77
Опыт использования Scala на Android Михаил Трищенков

Upload: valeriya-atamanova

Post on 12-Apr-2017

121 views

Category:

Mobile


1 download

TRANSCRIPT

Опыт использованияScala на Android

Михаил Трищенков

Что же я наделал?

Scala на Android

Транспорт Новосибирска

3

Транспорт Новосибирска

4

Транспорт Новосибирска

https://play.google.com/store/apps/details?id=net.kriomant.gortrans

https://github.com/kriomant/gortrans

5

О боже, но почему?

Scala на Android

Но почему?

Вывод типов

Локальные функции и типы

Кортежи и прочая функциональщина

«==» работает как положено

7

Анонимные функции

Анонимные функции

9

class Items { def mapSum(func: Item => Int): Int = { var sum = 0 for (item <- this.items) sum += func(item) sum }}

mapSum(item => item.weight)mapSum(_.weight)mapSum { item => item.weight}

Анонимные функции

Ну лямбды, а где их использовать?

10

val shownRoutes = infoWindowMarkers .filter(_.isShown) .map { m => val info = vehicleMarkers(m).info info.routeId }

Анонимные функции

Больше сахара!

11

val shownRoutes = for { m <- infoWindowMarkers if m.isShown info = vehicleMarkers(m).info} yield info.routeId

Анонимные функции

Изменяемый scope

12

val items = Array(1, 2, 4)var sum = 0items.foreach { item => sum += item}

Безопасность

Java: null

14

Route getRoute(String name, Integer order);

String getSchedule(String route) {Route route = getRoute("13a", null);String schedule = null;if (route = null) { schedule = route.getSchedule() }return schedule;}

Scala: Option

15

def getRoute(name: String, order: Option[Int]): Option[Route]

def getSchedule(route: String): Option[String] = {getRoute(route, None).map(_.schedule)}

for { first <- getOptValue1() if first >= 0 second <- getOptValue2()} yield first + second

Java: read-only collections

16

List<String> getStops() { return Collections.unmodifiableList(this.stops);}

List<String> list = route.getStops();list.add("stop");// BOOM! UnsupportedOperationException

List<String> getStops() { return new ArrayList<String>(this.stops);}

Scala: read-only interface / immutable collection

17

def getStops: collection.Seq[String] = stops

val stops = route.getStopsstops += "stop"// error: value += is not a member of Seq[String].

val extendedStops = stops :+ "stop"

Scala: immutable by default

18

collection.List

collection.immutable.List = List

collection.mutable.List

Scala: immutable by default

valimmutable collectionscase classes

19

Case classes &pattern matching

Simple Java class

21

class File { String name; long size; bool executable;

File(String name, long size, bool executable) { this.name = name; this.size = size; this.executable = executable; }

@Override bool equals(Object other) { … }

Initializers

22

class File(name: String, size: Long, executable: Boolean) { def getName: String = name def getSize: Long = size def isExecutable: Boolean = executable}

val file = new File("autoexec.bat", 1337, false)println(file.getName)

Initializers

23

class File(val name: String, val size: Long, val executable: Boolean)

val file = new File("autoexec.bat", 1337, false)println(file.name)

Case class

24

case class File(name: String, size: Long, executable: Boolean)

val file = File("autoexec.bat", 1337, false)println(file.name)val updated = file.copy(size = 1400)

file match { case File("autoexec.bat", _, _) => nostalgia.on() case File(name, _, _) => ordinary_file()}

Traits

Mixins: добавляем NavigationDrawer

26

Mixins: добавляем NavigationDrawer

27

@Override public void onCreate(Bundle savedInstanceState) { mDrawerToggle = new ActionBarDrawerToggle(…);}@Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mDrawerToggle.syncState();}@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig);}

@Override public boolean onOptionsItemSelected(MenuItem item) {

Mixins: добавляем NavigationDrawer

28

trait HavingSidebar extends Activity { // Interface var drawerLayout: Int def onDrawerShown(): Unit def onDrawerHidden(): Unit

// Implementationoverride def onCreate(savedInstanceState: Bundle): Unit = { super.onCreate(savedInstanceState) mDrawerToggle = new ActionBarDrawerToggle(…)}var mDrawerToggle: ActionBarDrawerToggle = null …

Mixins: добавляем NavigationDrawer

29

class MyActivity extends Activity with HavingSidebar { var drawerLayout: Int = R.layout.drawer

override def onDrawerShown(): Unit = { … }

… }

Curried functions +Call-by-name evaluation

Curried function: closing

31

type Closable = {def close()}

def closing[R <: Closable, T](resource: R)(block: R => T): T = { try { block(resource) } finally { if (resource != null) resource.close() }}

closing(db.query(…)) { cursor => … }

Curried function: edit

32

def edit(sp: SharedPreferences) (block: SharedPreferences.Editor => Unit) = { val editor = sp.edit() block(editor) editor.commit()}

edit(prefs) { editor => editor.putLong(…) editor.remove(…)}

Call-by-name-evaluation: retry

33

def retryOnceIfEmpty(f: => String): String = { val res = f if (res.nonEmpty) res else f}

val jsonStr = retryOnceIfEmpty { client.fetchRouteJson(id)}

Макросы

Макросы: sqlite.insert

35

def addEvent(timestamp: Date, kind: String, ref: Option[Long], data: String): Long = { import EventsTable.Columns._ val values = new ContentValues values.put(DATE_TIME, timestamp.getTime) values.put(KIND, kind) values.put(REF, ref.orNull) values.put(DATA, data) db.insertOrThrow(EventsTable.NAME, null, values)}

Макросы: sqlite.insert

36

def addEvent(timestamp: Date, kind: String, ref: Option[Long], data: String): Long = { import EventsTable.Columns._ EventsTable.insert( DATE_TIME -> timestamp.getTime, KIND -> kind, REF -> ref, DATA -> data )}

Макросы: inject view

37

class MyActivity extends AppCompatActivity { override def onCreate(savedInstanceState: Bundle): Unit = { super.onCreate(savedInstanceState)

setContentView(R.layout.account_list_activity) AutoInjector.injectViews() … }

@InjectView(R.id.account_list) private[this] var accountList: ListView = _}

Макросы: inject view

38

def injectViewsImpl(c: Context)(): c.Expr[Unit] = { val selfClass = c.internal.enclosingOwner.asMethod.owner.asType val theType = selfClass.toType val injections = for { decl <- theType.decls annotation <- decl.annotations.find { a => a.tree.tpe =:= c.weakTypeOf[InjectView] } } yield { ... q"""this.$decl = this.findViewById($id).asInstanceOf[$viewType]""" } c.Expr[Unit](q"{..$injections}")}

Implicits

Implicits: преобразование типов

40

okButton.setOnClickListener(new OnClickListener { def onClick(view: View) { doSmth() }})

Implicits: преобразование типов

41

implicit class Function0ToOnClickListener(f: () => Unit) extends OnClickListener {

override def onClick(v: View): Unit = f()}

okButton.setOnClickListener { () => doSmth() }

Implicits: extension methods

42

implicit class ViewExt(view: View) {

override def onClick(block: => Unit): Unit = { view.setOnClickListener(new OnClickListener { def onClick(v: View) { block } }) }

}

okButton.onClick { doSmth() }

Implicits: неявные параметры

43

def du(arg: Int, logger: Logger): Foo = { … }def duHast(arg: String, logger: Logger): Bar = { … }def duHastMich(foo: Foo, bar: Bar, logger: Logger): Baz = { … }

val logger = Logger(…)val baz = duHastMich(du(2, logger), duHast("yes", logger), logger)

Implicits: неявные параметры

44

def du(arg: Int)(implicit logger: Logger): Foo = { … }def duHast(arg: String) (implicit logger: Logger): Bar = { … }def duHastMich(foo: Foo, bar: Bar) (implicit logger: Logger): Baz = {…}

implicit val logger = Logger(…)val baz = duHastMich(du(2), duHast("yes"))

Как собирать?

Scala на Android

Как собирать?

46

SBT

Стандарт для Scala-проектовhttp://www.scala-sbt.org

Есть сторонний Android-плагин

47

SBT + Android

Плагины:

https://github.com/jberkel/android-plugin (устарел)

https://github.com/pfn/android-sdk-plugin

48

SBT

49

Перегрузка операторов

Ленивые вычисления

Лямбды

Макросы

Пример конфигурации SBT

50

lazy val core = Project("core", file("core"), settings = Defaults.defaultSettings ++ Seq( apiLevel := 15, platformName in Android := s"android-${(apiLevel in Android).value}", useProguard in Android := true, proguardOption in Android ~= { _ + " -dontnote scala.** " } libraryDependencies += "org.ccil.cowan.tagsoup" % "tagsoup" % "1.2", …

Gradle

Популярная система сборки для Java ине толькоhttp://gradle.org

Android-плагин поддерживается Google

Есть сторонний Scala-плагин

51

Gradle

http://developer.android.com/intl/ru/tools/building/plugin-for-gradle.html

https://github.com/saturday06/gradle-android-scala-plugin

52

Конфигурация Gradle

53

buildscript { dependencies { classpath 'com.android.tools.build:gradle:1.3.1' classpath 'jp.leafytree.gradle:gradle-android-scala-plugin:1.4' }}apply plugin: 'com.android.application'apply plugin: 'jp.leafytree.android-scala'

android { compileSdkVersion 'android-23' buildToolsVersion '23.0.1'

defaultConfig {

А как же родимый Java-код,либы, вот это все?

Scala на Android

Поддержка IDE

Scala на Android

IntelliJ IDEA

Поддерживает SBT-проекты

Поддерживает Gradle-проекты

Официальная IDE для Android

Официальный Scala-плагин от JetBrains

56

Небось отладка сломается?

IntelliJ IDEA

А рефакторинг?

IntelliJ IDEA

IntelliJ IDEA: радости

Автодополнение, включая implicits

Отладка, поддерживается Scala-специфика

Рефакторинг

Fast Scala Compiler — демон компиляции

62

IntelliJ IDEA: печали

Сброс настроек, не поддерживаемых системой сборки

Scala SDK

Настройки путей

63

За что мне все это?

Scala на Android

Переопределение vararg-методов

https://issues.scala-lang.org/browse/SI-1459

65

public abstract class AsyncTaskBridge<Progress, Result> extends AsyncTask<Void, Progress, Result> { @Override public Result doInBackground(Void... params) { return doInBackgroundBridge(); } protected abstract Result doInBackgroundBridge(); …

Доступа к protected static членам

https://issues.scala-lang.org/browse/SI-1806 (закрыт)

ItemizedOverlay, View.mergeDrawableStates

66

abstract public class ViewStaticBridge extends View { public ViewStaticBridge(Context context) { super(context); }

public static int[] mergeDrawableStates(int[] baseState, int[] additionalState) { return View.mergeDrawableStates(baseState, additionalState); }}

Parcellable::CREATOR

67

case class User(age: Int, name: String) extends Parcelable { protected this(source: Parcel) = this(source.readInt(), source.readString()) override def describeContents() = 0 override def writeToParcel(destination: Parcel, flags: Int) { … }}

object User{ override val CREATOR = new Parcelable.Creator[User] { override def createFromParcel(source: Parcel) = new User(source) override def newArray(size: Int) = new Array[User](size) }}

65K Reference Limit

Scala поможет быстрее достичь высот!

68

Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536

65K Reference Limit

Стандартная библиотека: много классов, много методов;в 2.10 стандартную библиотеку разделили

ProGuard нужен даже для debug-сборок

Multi-DEX — не использовал

linearAlloc bug

Размер gortrans — 1,5 Мб

69

ProGuardТребуется несколько настроек, чтобы не вырезалисьнужные части стандартной библиотеки

70

-dontwarn scala.concurrent.forkjoin.**-dontwarn scala.concurrent.impl.AbstractPromise-dontwarn scala.concurrent.util.Unsafe-dontwarn scala.reflect.**

# See https://issues.scala-lang.org/browse/SI-5397-keep class scala.collection.SeqLike { public protected *;}…

Скорость компиляции

Scala компилирует медленнее Java в 2-10 раз

Частично проблема решается FSC (Fast Scala Compilerdaemon) и sbt incremental compilation

Усугубляется необходимостью запуска ProGuard (можнорешить предустановкой Scala в эмуляторе)

71

Потребление ресурсов

Scala провоцирует использовать много ClosuresНо никто не мешает использовать Java-like код вкритичных точках

Макросы позволяют создавать zero-cost абстракции

72

Библиотечные зависимости

Source-level совместимость между минорными версиямиScala

Требуется пересборка библиотек для каждой минорнойверсии

73

Заключение

Scala на Android

Что мы получаем

Более выразительный язык

Более типизированный

Компактный код

Дополнительные возможности переиспользования кода

75

Чем за это расплачиваемся

Медленная сборка

Поддержка новых возможностей системы сборки Androidзапаздывает

76

Альтернативы

Java 8 lambdas for Java 5–7https://github.com/orfjackal/retrolambda

Kotlinhttp://kotlinlang.org/docs/tutorials/kotlin-android.htmlhttp://kotlinlang.org/docs/tutorials/android-plugin.html

android-apt + javapoet (used by Butterknife)

guava / apache-commons

77