-
Notifications
You must be signed in to change notification settings - Fork 0
Talk: SF Bay Area JRuby Meetup (September 22, 2011)
-
3 ways to do Ruby on Android
- Rhodes: Open source Ruby-based framework for cross-platform app development
- Scripting Layer 4 Android: Simplified API for various scripting languages (including JRuby)
- Ruboto: Goal - Make JRuby a first-class development language for Android
-
Basics of Android
- Linux kernel, Dalvik VM
- Activity - Visual component, roughly one screen
- Service - Background process
- Others - BroadcastReceiver and ContentProvider
- Intent - "I want to do something" abstraction layer
- Manifest - Informs system about activities, services, permissions, etc.
-
Challenges
- Mobile hardware limitations
- Dalvik VM - different byte code, stack limits
- Passing control for callbacks to scripts
- Excess components of JRuby
- Android resource framework
-
Basics of Ruboto
- Ruboto IRB - App for running scripts
- Ruboto Core - Generates apps (set up callbacks)
- Shared
- Core package shared on device
- ruboto.rb - Device script encapsulating JRuby/Android intaraction
-
Ruboto IRB
-
Webrick server (demos use of stdlib, notification, service)
-
Standard demo script to obtain remote access: demo-irb-server.rb
-
Extend demo to get access to demo scripts. Note: this assumes a subdirectory called "demo" that I added to contain the demo scripts, also uses SyntaxHighlighter 3.0.83.
if $server class DemoListServlet < HTTPServlet::AbstractServlet def list_page %Q[ <html> <body> <h2>Demos</h2> #{yield} </body> </html> ] end def do_GET(req, resp) resp.content_type = "text/html" resp.body = list_page do Dir.glob("demo/*.rb").sort.map do |i| unless File.directory?(i) $server.mount("/#{i}", DemoServlet, i) "<a href='/#{i}'>#{i.split('.')[1].split('-').map(&:capitalize).join(' ')}</a><br/>" end end.join end end def do_POST(req, resp) do_GET(req, resp) end end class DemoServlet < HTTPServlet::AbstractServlet def body(name="untitled.rb", script="") %Q[ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript" src="/demo/shCore.js"></script> <script type="text/javascript" src="/demo/shBrushRuby.js"></script> <link type="text/css" rel="stylesheet" href="/demo/shCoreDefault.css"/> <script type="text/javascript">SyntaxHighlighter.all();</script> </head> <body style="background: white; font-family: Helvetica"> <h1>#{name.split('.')[1].split('-').map(&:capitalize).join(' ')}</h1> <pre class="brush: ruby; toolbar: false; auto-links: false #{name.split('.').length > 3 ? ('; highlight: ' + name.split('.')[2].split('-').map(&:to_i).inspect) : ''}">#{HTMLUtils.escape(script)}</pre> <form action='/' method='post'> <input type='submit' value='Eval' /> <br/> <textarea id='code' name='code' rows='1' cols='1'>#{HTMLUtils.escape(script)}</textarea> </form> </body> </html> ] end def do_GET(req, resp) resp.content_type = "text/html" name = @options[0] || "untitled.rb" script = File.exists?(name) ? IO.read(name) : "" resp.body = body(name, script) end end $server.mount("/demos", DemoListServlet, nil) puts "Demos mounted: http://#{ip_address}:#{SERVER_PORT}/demos" else puts "Mount failed: No server running" end
-
-
Intents: Opening 3rd-party apps
-
Camera
$activity.startActivity( Java::android.content.Intent.new( Java::android.provider.MediaStore::ACTION_IMAGE_CAPTURE))
-
Browser
java_import "android.content.Intent" java_import "android.net.Uri" $activity.startActivity(Intent.new(Intent::ACTION_VIEW, Uri.parse("http://ruboto.org")))
-
Map
java_import "android.content.Intent" java_import "android.net.Uri" $activity.startActivity( Intent.new( Intent::ACTION_VIEW, Uri.parse("geo:0,0?q=engine+yard+san+francisco") ) )
-
-
Activity:
-
Domo Arigato, Mr. Ruboto (basic ruboto activity)
require 'ruboto/activity' java_import "android.view.Gravity" java_import "android.widget.TextView" $activity.start_ruboto_activity "$hello_world" do setTitle "Domo Arigato" def on_create(bundle) tv = TextView.new(self) tv.text = "Domo Arigato" tv.text_size = 64 tv.gravity = (Gravity::CENTER_HORIZONTAL | Gravity::CENTER_VERTICAL) tv.on_click_listener = proc{|view| view.text = "Mr. Ruboto"} self.content_view = tv end end
-
Domo Arigato, Mr. Ruboto version 2 (use of widgets)
require 'ruboto/activity' require 'ruboto/widget' java_import "android.view.Gravity" ruboto_import_widgets :TextView, :LinearLayout $activity.start_ruboto_activity "$hello_world" do setTitle "Domo Arigato" def domo_text_view text_view:layout => {:width= => :fill_parent, :height= => :fill_parent, :weight= => 1.0}, :text => "Domo Arigato", :text_size => 48, :gravity => (Gravity::CENTER_HORIZONTAL | Gravity::CENTER_VERTICAL), :on_click_listener => (proc do |view| view.text = ("Mr. Ruboto" == view.text ? "Domo Arigato" : "Mr. Ruboto") end) end def on_create(bundle) self.content_view = linear_layout(:orientation => :vertical) do linear_layout(:layout => {:width= => :fill_parent, :height= => :fill_parent, :weight= => 1.0}) do domo_text_view domo_text_view end linear_layout(:layout => {:width= => :fill_parent, :height= => :fill_parent, :weight= => 1.0}) do domo_text_view domo_text_view end end end end
-
Camera/Picture (implements interface), standard demo-camera.rb
-
Adding camera click mount to server
if $server $server.mount_proc('/click') do |req, resp| f = $activity.take_picture sleep 2 # wait for picture to be written to the file resp.content_type = "image/jpg" resp.body = IO.read f end end
-
-
Open GL (implements interface, shows open GL) demo-opengl.rb
-
-
Accessing device features
-
Sensors
java_import "android.content.Intent" java_import "android.net.Uri" java_import "android.location.LocationManager" java_import "android.content.Context" l = $activity.getSystemService(Context::LOCATION_SERVICE). getLastKnownLocation(LocationManager::NETWORK_PROVIDER) $activity.startActivity( Intent.new( Intent::ACTION_VIEW, Uri.parse("geo:#{l.latitude},#{l.longitude}") ) )
-
Voice input
require 'ruboto/activity' require 'ruboto/util/toast' java_import "android.speech.RecognizerIntent" java_import "android.content.Intent" java_import "android.net.Uri" $activity.start_ruboto_activity "$speech_demo" do intent = Intent.new(RecognizerIntent::ACTION_RECOGNIZE_SPEECH) intent.putExtra(RecognizerIntent::EXTRA_LANGUAGE_MODEL, RecognizerIntent::LANGUAGE_MODEL_FREE_FORM) intent.putExtra(RecognizerIntent::EXTRA_PROMPT, "Speech recognition demo") startActivityForResult(intent, 1) def on_activity_result(requestCode, resultCode, data) if resultCode == Activity::RESULT_OK results = data.get_string_array_list_extra(RecognizerIntent::EXTRA_RESULTS) puts results.join("\n") startActivity(Intent.new(Intent::ACTION_VIEW, Uri.parse("geo:0,0?q=" + results[0].gsub(" ", "+")))) end finish end end
-
-
Multiple Activities: "One Script to Rule Them All" (create a script to list and run our demos)
require "ruboto.rb" java_import "android.app.AlertDialog" $main_binding = self.instance_eval{binding} def launch_list(context, title, list, &block) ruboto_import_widgets :ListView context.start_ruboto_activity "$list" do setTitle title @list = list @block = block def on_create(bundle) self.content_view = list_view :list => @list, :on_item_click_listener => proc{|av, v, p, i| @block.call(@list, p)} end end end def show_demo(context, file, name) java_import "android.view.Gravity" ruboto_import_widgets :EditText, :LinearLayout, :Button context.start_ruboto_activity "$demo" do getWindow.setSoftInputMode( android.view.WindowManager::LayoutParams::SOFT_INPUT_STATE_VISIBLE | android.view.WindowManager::LayoutParams::SOFT_INPUT_ADJUST_RESIZE) setTitle name @file = file def on_create(bundle) self.content_view = linear_layout(:orientation => :vertical) do button :text => "Eval", :layout => {:width= => :fill_parent}, :on_click_listener => proc{eval_code @tv.text.to_s} @tv = edit_text :layout => {:height= => :fill_parent}, :text => load_demo(@file), :gravity => Gravity::TOP, :horizontally_scrolling => true, :text_size => 24 end end end end def eval_code(code) begin $main_binding.eval(code) rescue => e AlertDialog::Builder.new($activity). setTitle("Error"). setMessage(e.backtrace.join("\n")). setPositiveButton("Ok", nil). create. show end end def load_demo(name) rv = "" if File.exists?(name) rv = IO.read(name) else buff_size = 0x2000 buff = Java::byte[buff_size].new i = $activity.getAssets.open(name) i = java.io.BufferedInputStream.new(i, buff_size) x = i.read(buff, 0, buff_size) while x != -1 rv += String.from_java_bytes(x == buff_size ? buff : buff[0..(x-1)]) x = i.read(buff, 0, buff_size) end end rv end # Check file system or read from assets demo_files = nil if File.directory?("demo") demo_files = Dir.glob("demo/*.rb").sort else demo_files = $activity.getAssets.list("demo").select{|i| i[-3..-1] == ".rb"}.sort.map{|i| "demo/#{i}"} end demo_names = demo_files.map{|i| i.split('.')[1].split('-').map(&:capitalize).join(' ')} launch_list($activity, "Please pick a demo", demo_names) do |list, pos| show_demo $list, demo_files[pos], demo_names[pos] end
-
-
Ruboto Core
-
Development requirements
- JDK & Ant
- Ruby, Rubygems, Rake
- Android SDK & Platforms
- Gems: ruboto-core & jruby-jars
- Path: JDK, Ant, Android, Gems bin
-
Note: To match the version of ruboto.rb in Ruboto IRB v0.6 I have created a fork of ruboto-core. Use this gem.
-
Generating the basic app
ruboto gen app --package org.rubyandroid.demo.jruby_meetup_demo --target=android-12 --min-sdk=android-7 cd jruby_meetup_demo rake debug -
Modify to hold our demo app
-
Copy demo scripts to assets
-
Add About Dialog script as first demo
require 'ruboto/activity' require 'ruboto/widget' ruboto_import_widgets :ScrollView, :TextView $activity.start_ruboto_dialog "$about" do setTitle "About The JRuby Meetup Demo" def on_create(bundle) self.content_view = scroll_view do tv = $activity.text_view :padding => [5,5,5,5], :text => @message, :text_size => 18, :text_color => Java::android.graphics.Color::WHITE Java::android.text.util::Linkify.addLinks(tv, Java::android.text.util::Linkify::ALL) end end @message = %Q[This is a demo of Ruboto (JRuby on Android) created by Scott Moyer for the SF Bay Area JRuby Meetup (September 22, 2011). It consists of a series of demo scripts that you can view and run. For more information: Wiki of demo: https://github.com/ruboto/ruboto-core/wiki/Talk%3A-SF-Bay-Area-JRuby-Meetup-%28September-22%2C-2011%29 Meetup: http://www.meetup.com/SF-Bay-Area-JRuby-Meetup Ruboto Community: http://www.ruboto.org JRuby: http://jruby.org Android Development: http://developer.android.com] end
-
Generate callback interfaces
ruboto gen interface android.view.SurfaceHolder.Callback --name RubotoSurfaceHolderCallback ruboto gen interface android.opengl.GLSurfaceView.Renderer --name RubotoGLSurfaceViewRenderer -
Request permissions through AndroidManifes.xml
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.CAMERA"/>
-
Tell the activities to manage their own orientation changes in AndroidManifes.xml
android:configChanges="orientation"
-
-
Create keystore to sign the app for release onto the Market
keytool -genkey -v -keystore meetup.keystore -alias meetup -keyalg RSA -keysize 2048 -validity 10000 -
Create a build.properties file to specify the keystore for signing
key.store=meetup.keystore key.alias=meetup -
Build release
rake release -
Getting to Market
- Requires a market account (one time $25)
- Will need:
- Screen shots (can use emulator)
- 512x512 image
- Description
-
- Key outstanding issues
- Start up speed
- Stack limitations
- Size (managed some now)
- Compiling ruby
- Generating byte code on the device
- Handling callbacks from libraries outside of core Android
- Links
- Getting involved
- Use it and let us know what is needed
- Create demo scripts/tutorials (features, gems)
- Work on the outstanding problems