SlideShare a Scribd company logo
Tricks to Making a Realtime
SurfaceView Actually Perform in
Realtime
Maarten Edgar
Hello, my name is …
Hello, my name is …
Maarten Edgar
What we’ll cover
SurfaceViews:
• Why
• When
• What
• How
• Hard earned lessons
Why use a SurfaceView?
SurfaceView
GL_SurfaceView
TextureView
SurfaceTexture
View
What is a SurfaceView?
A View which gives you access to a
Surface using .getHolder(), which is
drawn on a seperate thread and is
double/triple buffered behind the
scenes.
It cuts holes and displays underneath
the window it is in.
How to use it:
• Setup
• Threads vs Runnables and other
control mechanisms
• Loops
• UI communication
• Tips
Setup
• Activity/View
• SurfaceView and its Thread
Setup: Activity and View
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set flags as needed
getWindow().setFormat(PixelFormat.RGBA_8888);
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
setContentView(R.layout.activity_game);
// get handles to the View from XML, and its Thread
mCSurfaceView = (MySurfaceView) findViewById(R.id.surfaceview);
setSurfaceType(View.LAYER_TYPE_SOFTWARE);
mSurfaceViewThread = mSurfaceView.getThread();
createInputObjectPool();
Your SurfaceView class
public class ChiBlastSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
public ChiBlastSurfaceView(Context context) {
super(context);
mSurfaceCreated = false;
touchBool = true;
// register our interest in hearing about changes to our surface
SurfaceHolder holder = getHolder();
holder.addCallback(this);
myHandler = new MyInnerHandler(this);
// create thread only; it's started in surfaceCreated()
thread = new ChiBlastSurfaceViewThread(holder, context, myHandler);
setFocusable(true); // make sure we get key events
}
Your SurfaceView callbacks 1/3
SurfaceHolder.Callback:
@Override
public void surfaceCreated(SurfaceHolder holder) {
// start the thread here so that we don't busy-wait
in run() waiting for the surface to be created
if (mSurfaceCreated == false)
{
createThread(holder);
mSurfaceCreated = true;
touchBool = true;
}
}
Your SurfaceView callbacks 2/3
SurfaceHolder.Callback:
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mSurfaceCreated = false;
cleanupResource();
terminateThread();
}
Your SurfaceView callbacks 3/3
SurfaceHolder.Callback:
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
thread.setSurfaceSize(width, height);
}
Setup: driving the SurfaceView
Runnables, thread and loops, oh my!
Setup: Thread
public class ChiBlastSurfaceViewThread extends Thread {
public ChiBlastSurfaceViewThread(SurfaceHolder surfaceHolder,
Context context, Handler handler) {
// get handles to some important objects
mSurfaceHolder = surfaceHolder;
mSurfaceHolder.setFormat(PixelFormat.RGBA_8888);
mContext = context;
res = context.getResources();
//any other initialization:
ops = new BitmapFactory.Options();
ops.inPurgeable = true;
ops.inDensity = 0;
ops.inDither = false;
ops.inScaled = false;
ops.inPreferredConfig = Bitmap.Config.ARGB_8888;
ops.inJustDecodeBounds = false;
}
@Override
public void run() {
while (mRun) {
Canvas c = null;
try {
// update game state
processInput();
//if (mMode == STATE_SCROLL_MAP)
if (mMode != STATE_PAUSE)
{
updatePhysics(timeDiff);
}
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
doDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
Setup: Thread
The Thread and your Activity
What does this now mean for your
Activity?
or
How do we make this fit into the
Android Lifecycle?
The Thread and your Activity
@Override
protected void onPause() {
super.onPause();
// pause game when Activity pauses
mSurfaceView.getThread().pause();
mSurfaceView.terminateThread();
System.gc();
}
The Thread and your Activity
@Override
protected void onResume()
{
super.onResume();
if (mSurfaceView.mSurfaceCreated)
{
mSurfaceView.createThread(mSurfaceView.getHolder());
setSurfaceType(View.LAYER_TYPE_SOFTWARE);
}
mSurfaceView.SetTouch(true);
}
The Thread and your Activity
@Override
protected void onRestoreInstanceState(Bundle inState) {
// just have the View's thread load its state from our Bundle
if (mSurfaceView.mSurfaceCreated)
{
mSurfaceView.createThread(mSurfaceView.getHolder());
setSurfaceType(View.LAYER_TYPE_SOFTWARE);
}
mSurfaceViewThread.restoreState(inState);
}
The main loop
• AFAFP
• Fixed step
@Override
public void run() {
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
timeDiff = System.currentTimeMillis()+50;
sleepTime = 0;
while (mRun) {
Canvas c = null;
try {
beginTime = System.currentTimeMillis();
framesSkipped = 0; // resetting the frames skipped
// update game state
processInput();
//if (mMode == STATE_SCROLL_MAP)
if (mMode != STATE_PAUSE)
{
updatePhysics(timeDiff);
}
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
doDraw(c);
}
The main loop 1/3
The main loop 2/3
// calculate how long did the cycle take
timeDiff = System.currentTimeMillis() - beginTime;
// calculate sleep time
sleepTime = (int)(FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
try {
// send the thread to sleep for a short period
// very useful for battery saving
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// we need to catch up
// update without rendering
processInput();
if (mMode != STATE_PAUSE)
{
updatePhysics(timeDiff);
}
// add frame period to check if in next frame
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
The main loop 3/3
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
UI Communication
Use a Handler:
static class MyInnerHandler extends Handler {
private final WeakReference<ChiBlastSurfaceView> mView;
MyInnerHandler(ChiBlastSurfaceView aView) {
mView = new WeakReference<ChiBlastSurfaceView>(aView);
}
@Override
public void handleMessage(Message m) {
ChiBlastSurfaceView theView = mView.get();
theView.mStatusText.setText(m.getData().getString("text"));
if (m.getData().getInt("viz") == View.VISIBLE)
{
theView.mStatusText.setVisibility(View.VISIBLE);
//mStatusText.setAnimation(displayTextAnim);
//mStatusText.startAnimation(displayTextAnim);
}
else
{
if (m.getData().getInt("viz") == View.INVISIBLE)
{
theView.mStatusText.setVisibility(View.INVISIBLE);
theView.mStatusText.setAnimation(null);
}
else if (m.getData().getInt("viz") == View.GONE)
{
theView.mStatusText.setVisibility(View.GONE);
}
}
theView.mStatusText.invalidate();
}
}
Setup: Cleanup
public void terminateThread ()
{
boolean retry = true;
thread.setRunning(false);
while (retry) {
try
{
thread.join();
retry = false;
}
catch (InterruptedException e)
{
}
//break; //THIS BREAKS IT ON PUSHING HOME
}
//thread = null; //THIS BREAKS IT ON PUSHING HOME
}
Tips
• Input buffer
• Object creation
• Scaling
• Drawing, bitmaps and other dirty
things
Tips: input buffer in SVActivity
private void createInputObjectPool() {
inputObjectPool = new ArrayBlockingQueue<InputObject>(INPUT_QUEUE_SIZE);
for (int i = 0; i < INPUT_QUEUE_SIZE; i++) {
inputObjectPool.add(new InputObject(inputObjectPool));
}
}
public class InputObject {
public static final byte EVENT_TYPE_KEY = 1;
public static final byte EVENT_TYPE_TOUCH = 2;
public static final int ACTION_KEY_DOWN = 1;
public static final int ACTION_KEY_UP = 2;
public static final int ACTION_TOUCH_DOWN = MotionEvent.ACTION_DOWN;
public static final int ACTION_TOUCH_POINTER_DOWN = MotionEvent.ACTION_POINTER_DOWN;
//public static final int ACTION_TOUCH_POINTER_2_DOWN = MotionEvent.ACTION_POINTER_2_DOWN;
public static final int ACTION_TOUCH_MOVE = MotionEvent.ACTION_MOVE;
public static final int ACTION_TOUCH_UP = MotionEvent.ACTION_UP;
public static final int ACTION_TOUCH_POINTER_UP = MotionEvent.ACTION_POINTER_UP;
//public static final int ACTION_TOUCH_POINTER_2_UP = MotionEvent.ACTION_POINTER_2_UP;
public ArrayBlockingQueue<InputObject> pool;
public byte eventType;
public long time;
public int action;
public int keyCode;
public int x;
public int y;
public int x2;
public int y2;
public int pointerID;
public int pointerIndex;
public int pointerIndex2;
InputObject 1/5
InputObject 2/5
public InputObject(ArrayBlockingQueue<InputObject> pool) {
this.pool = pool;
}
public void useEvent(KeyEvent event) {
eventType = EVENT_TYPE_KEY;
int a = event.getAction();
switch (a) {
case KeyEvent.ACTION_DOWN:
action = ACTION_KEY_DOWN;
break;
case KeyEvent.ACTION_UP:
action = ACTION_KEY_UP;
break;
default:
action = 0;
}
time = event.getEventTime();
keyCode = event.getKeyCode();
}
public void useEvent(MotionEvent event) {
eventType = EVENT_TYPE_TOUCH;
int a = event.getAction();
switch (a) {
case MotionEvent.ACTION_DOWN:
action = ACTION_TOUCH_DOWN;
break;
case MotionEvent.ACTION_POINTER_DOWN:
action = ACTION_TOUCH_POINTER_DOWN;
break;
case MotionEvent.ACTION_POINTER_2_DOWN:
action = ACTION_TOUCH_POINTER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
action = ACTION_TOUCH_MOVE;
break;
case MotionEvent.ACTION_UP:
action = ACTION_TOUCH_UP;
break;
case MotionEvent.ACTION_POINTER_UP:
action = ACTION_TOUCH_POINTER_UP;
break;
case MotionEvent.ACTION_POINTER_2_UP:
action = ACTION_TOUCH_POINTER_UP;
break;
default:
action = -1;
}
InputObject 3/5
InputObject 4/5
time = event.getEventTime();
pointerIndex = (event.getAction() &
MotionEvent.ACTION_POINTER_ID_MASK) >>
MotionEvent.ACTION_POINTER_ID_SHIFT;
pointerID = event.getPointerId(pointerIndex);
x = (int) event.getX(pointerIndex);
y = (int) event.getY(pointerIndex);
if (event.getPointerCount() > 1)
{
pointerIndex2 = pointerIndex== 0 ? 1 : 0;
x2 = (int)event.getX(pointerIndex2);
y2 = (int)event.getY(pointerIndex2);
}
}
InputObject 5/5
public void useEventHistory(MotionEvent event, int historyItem) {
eventType = EVENT_TYPE_TOUCH;
action = ACTION_TOUCH_MOVE;
time = event.getHistoricalEventTime(historyItem);
pointerIndex = (event.getAction() &
MotionEvent.ACTION_POINTER_ID_MASK) >>
MotionEvent.ACTION_POINTER_ID_SHIFT;
pointerID = event.getPointerId(pointerIndex);
x = (int) event.getHistoricalX(pointerIndex, historyItem);
y = (int) event.getHistoricalY(pointerIndex, historyItem);
if (event.getPointerCount() > 1)
{
pointerIndex2 = pointerIndex== 0 ? 1 : 0;
x2 = (int) event.getHistoricalX(pointerIndex2, historyItem);
y2 = (int) event.getHistoricalY(pointerIndex2, historyItem);
}
}
public void returnToPool() {
pool.add(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
// history first
int hist = event.getHistorySize();
if (hist > 0)
{
// add from oldest to newest
for (int i = 0; i < hist; i++)
{
//for (int i = hist-1; i > -1; i--) {
InputObject input = inputObjectPool.take();
input.useEventHistory(event, i);
mSurfaceViewThread.feedInput(input);
}
}
// current last
InputObject input = inputObjectPool.take();
input.useEvent(event);
mSurfaceViewThread.feedInput(input);
} catch (InterruptedException e) {
}
// don't allow more than 60 motion events per second
try {
Thread.sleep(16);
} catch (InterruptedException e) {
}
return true;
}
Back to the activity:
public void feedInput(InputObject input) {
synchronized(inputQueueMutex) {
try {
inputQueue.put(input);
} catch (InterruptedException e) {
//Log.e(TAG, e.getMessage(), e);
}
}
}
private void processInput() {
synchronized(inputQueueMutex) {
ArrayBlockingQueue<InputObject> inputQueue = ChiBlastSurfaceView.inputQueue;
while (!inputQueue.isEmpty()) {
try {
InputObject input = inputQueue.take();
if (input.eventType == InputObject.EVENT_TYPE_KEY) {
//processKeyEvent(input);
} else if (input.eventType == InputObject.EVENT_TYPE_TOUCH) {
processMotionEvent(input);
}
input.returnToPool();
} catch (InterruptedException e) {
//Log.e(TAG, e.getMessage(), e);
}
}
}
}
And in the SurfaceView.Thread:
Tips: object creation
Tips: object creation
Just don’t do it.
Tips: object creation
Or do it up front.
No matter how odd that sometimes
may seem.
Tips: scaling
Two types of scaling:
• Realtime whole view SV scaling
only works from Android N
• Fixed scaling (as done in Unreal
Tournament 3)
Tips: scaling
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//thread.setSurfaceSize(width, height);
if (mCanvasWidth != width)
{
int scaledWidth = (int)(width*0.75f);
int scaledHeight = (int)(height*0.75f);
if (scaledHeight != height)
{
yRatio = (float)(scaledHeight / (float)height);
xRatio = (float)(scaledWidth / (float)width);
}
holder.setFixedSize(scaledWidth, scaledHeight);
thread.setSurfaceSize(scaledWidth, scaledHeight);
}
}
Tips: drawing, bitmaps and other
dirty things
Tips: drawing, bitmaps and other
dirty things
In SurfaceView.Thread doDraw():
canvas.drawBitmap(mBackgroundImage,
null, fullscreenRect, mPicPaint);
Q&A
Thank you!
Maarten Edgar
lifeboatsoft@gmail.com
Resources:
https://source.android.com/devices/graphics/architecture.html
https://github.com/google/grafika

More Related Content

PDF
Knock, knock, who is there? Doze.
PDF
Engineering Wunderlist for Android - Ceasr Valiente, 6Wunderkinder
PDF
Modern Android app library stack
PDF
Deep Dive into Zone.JS
PDF
mDevCamp - The Best from Google IO
PPTX
Become a Garbage Collection Hero
PPTX
Become a GC Hero
ODP
Android App Development - 07 Threading
Knock, knock, who is there? Doze.
Engineering Wunderlist for Android - Ceasr Valiente, 6Wunderkinder
Modern Android app library stack
Deep Dive into Zone.JS
mDevCamp - The Best from Google IO
Become a Garbage Collection Hero
Become a GC Hero
Android App Development - 07 Threading

What's hot (19)

PPTX
Don't dump thread dumps
PDF
Why Task Queues - ComoRichWeb
PPTX
Taking advantage of the Amazon Web Services (AWS) Family
PDF
Advanced iOS Build Mechanics, Sebastien Pouliot
PPTX
GC Tuning & Troubleshooting Crash Course
PDF
Developing Async Sense
PDF
React, Redux and es6/7
PPTX
Troubleshooting real production problems
PPTX
MongoDB: tips, trick and hacks
PPTX
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
PDF
Cassandra Summit EU 2014 Lightning talk - Paging (no animation)
PDF
Third Party Auth in WebObjects
PPTX
RxJS In-Depth - AngularConnect 2015
PPTX
Angular 1 + es6
PDF
Adventures in Multithreaded Core Data
PDF
LJC Conference 2014 Cassandra for Java Developers
PPT
Troubleshooting performanceavailabilityproblems (1)
PDF
Snapshot clone-boot-presentation-final
PDF
Building Scalable Stateless Applications with RxJava
Don't dump thread dumps
Why Task Queues - ComoRichWeb
Taking advantage of the Amazon Web Services (AWS) Family
Advanced iOS Build Mechanics, Sebastien Pouliot
GC Tuning & Troubleshooting Crash Course
Developing Async Sense
React, Redux and es6/7
Troubleshooting real production problems
MongoDB: tips, trick and hacks
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
Cassandra Summit EU 2014 Lightning talk - Paging (no animation)
Third Party Auth in WebObjects
RxJS In-Depth - AngularConnect 2015
Angular 1 + es6
Adventures in Multithreaded Core Data
LJC Conference 2014 Cassandra for Java Developers
Troubleshooting performanceavailabilityproblems (1)
Snapshot clone-boot-presentation-final
Building Scalable Stateless Applications with RxJava
Ad

Viewers also liked (17)

PDF
Cognitive interaction using Wearables - Eyal herman, IBM
PDF
3 things every Android developer must know about Microsoft - Ido Volff, Micro...
PPTX
Creating killer apps powered by watson cognitive services - Ronen Siman-Tov, IBM
PDF
Android Application Optimization: Overview and Tools - Oref Barad, AVG
PDF
Android is going to Go! - Android and goland - Almog Baku
PDF
Write code that writes code! A beginner's guide to Annotation Processing - Ja...
PPTX
Will it run or will it not run? Background processes in Android 6 - Anna Lifs...
PPTX
Good Rules for Bad Apps - Shem magnezi
PDF
Intro to Dependency Injection - Or bar
PDF
Mobile SDKs: Use with Caution - Ori Lentzitzky
PDF
Context is Everything - Royi Benyossef
PPTX
Set it and forget it: Let the machine learn its job - Guy Baron, Vonage
PDF
Think Async: Understanding the Complexity of Multithreading - Avi Kabizon & A...
PDF
Knock knock! Who's there? Doze. - Yonatan Levin
PPTX
Optimize your delivery and quality with the right release methodology and too...
PDF
Android Continuous Integration and Automation - Enrique Lopez Manas, Sixt
PDF
Build an App with Blindfold - Britt Barak
Cognitive interaction using Wearables - Eyal herman, IBM
3 things every Android developer must know about Microsoft - Ido Volff, Micro...
Creating killer apps powered by watson cognitive services - Ronen Siman-Tov, IBM
Android Application Optimization: Overview and Tools - Oref Barad, AVG
Android is going to Go! - Android and goland - Almog Baku
Write code that writes code! A beginner's guide to Annotation Processing - Ja...
Will it run or will it not run? Background processes in Android 6 - Anna Lifs...
Good Rules for Bad Apps - Shem magnezi
Intro to Dependency Injection - Or bar
Mobile SDKs: Use with Caution - Ori Lentzitzky
Context is Everything - Royi Benyossef
Set it and forget it: Let the machine learn its job - Guy Baron, Vonage
Think Async: Understanding the Complexity of Multithreading - Avi Kabizon & A...
Knock knock! Who's there? Doze. - Yonatan Levin
Optimize your delivery and quality with the right release methodology and too...
Android Continuous Integration and Automation - Enrique Lopez Manas, Sixt
Build an App with Blindfold - Britt Barak
Ad

Similar to Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarten Edger (20)

PDF
Android Best Practices
PPT
Thread
KEY
Android workshop
PDF
33rd Degree 2013, Bad Tests, Good Tests
PDF
303 TANSTAAFL: Using Open Source iPhone UI Code
PDF
Useful Tools for Making Video Games - XNA (2008)
PPT
2012 JDays Bad Tests Good Tests
PDF
JavaScript Refactoring
PDF
.NET Multithreading and File I/O
PDF
I wanted to change the cloudsrectangles into an actuall image it do.pdf
PDF
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
PDF
JVM Mechanics: When Does the JVM JIT & Deoptimize?
PDF
Implementing new WebAPIs
PDF
Implementing New Web
PDF
Ten useful JavaScript tips & best practices
PDF
Improving android experience for both users and developers
PDF
Droidcon2013 android experience lahoda
ZIP
PPT
Open Cv 2005 Q4 Tutorial
PDF
Silicon Valley JUG: JVM Mechanics
Android Best Practices
Thread
Android workshop
33rd Degree 2013, Bad Tests, Good Tests
303 TANSTAAFL: Using Open Source iPhone UI Code
Useful Tools for Making Video Games - XNA (2008)
2012 JDays Bad Tests Good Tests
JavaScript Refactoring
.NET Multithreading and File I/O
I wanted to change the cloudsrectangles into an actuall image it do.pdf
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
JVM Mechanics: When Does the JVM JIT & Deoptimize?
Implementing new WebAPIs
Implementing New Web
Ten useful JavaScript tips & best practices
Improving android experience for both users and developers
Droidcon2013 android experience lahoda
Open Cv 2005 Q4 Tutorial
Silicon Valley JUG: JVM Mechanics

More from DroidConTLV (20)

PDF
Mobile Development in the Information Age - Yossi Elkrief, Nike
PDF
Doing work in the background - Darryn Campbell, Zebra Technologies
PDF
No more video loss - Alex Rivkin, Motorola Solutions
PDF
Mobile at Scale: from startup to a big company - Dor Samet, Booking.com
PDF
LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell
PDF
MVVM In real life - Lea Cohen Tannoudji, Lightricks
PDF
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
PDF
Building Apps with Flutter - Hillel Coren, Invoice Ninja
PDF
New Android Project: The Most Important Decisions - Vasiliy Zukanov
PDF
Designing a Design System - Shai Mishali, Gett
PDF
The Mighty Power of the Accessibility Service - Guy Griv, Pepper
PDF
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
PDF
Flutter State Management - Moti Bartov, Tikal
PDF
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
PDF
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
PDF
DroidconTLV 2019
PDF
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
PDF
Introduction to React Native - Lev Vidrak, Wix
PDF
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
PDF
Educating your app – adding ML edge to your apps - Maoz Tamir
Mobile Development in the Information Age - Yossi Elkrief, Nike
Doing work in the background - Darryn Campbell, Zebra Technologies
No more video loss - Alex Rivkin, Motorola Solutions
Mobile at Scale: from startup to a big company - Dor Samet, Booking.com
LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell
MVVM In real life - Lea Cohen Tannoudji, Lightricks
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
Building Apps with Flutter - Hillel Coren, Invoice Ninja
New Android Project: The Most Important Decisions - Vasiliy Zukanov
Designing a Design System - Shai Mishali, Gett
The Mighty Power of the Accessibility Service - Guy Griv, Pepper
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Flutter State Management - Moti Bartov, Tikal
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
DroidconTLV 2019
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
Introduction to React Native - Lev Vidrak, Wix
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
Educating your app – adding ML edge to your apps - Maoz Tamir

Recently uploaded (20)

PDF
Electronic commerce courselecture one. Pdf
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PPT
Teaching material agriculture food technology
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Empathic Computing: Creating Shared Understanding
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
KodekX | Application Modernization Development
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PPTX
Spectroscopy.pptx food analysis technology
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
Electronic commerce courselecture one. Pdf
Network Security Unit 5.pdf for BCA BBA.
Diabetes mellitus diagnosis method based random forest with bat algorithm
MIND Revenue Release Quarter 2 2025 Press Release
Understanding_Digital_Forensics_Presentation.pptx
Teaching material agriculture food technology
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
MYSQL Presentation for SQL database connectivity
Empathic Computing: Creating Shared Understanding
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Agricultural_Statistics_at_a_Glance_2022_0.pdf
KodekX | Application Modernization Development
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Spectroscopy.pptx food analysis technology
The Rise and Fall of 3GPP – Time for a Sabbatical?
Mobile App Security Testing_ A Comprehensive Guide.pdf
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton

Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarten Edger

  • 1. Tricks to Making a Realtime SurfaceView Actually Perform in Realtime Maarten Edgar
  • 2. Hello, my name is …
  • 3. Hello, my name is … Maarten Edgar
  • 4. What we’ll cover SurfaceViews: • Why • When • What • How • Hard earned lessons
  • 5. Why use a SurfaceView? SurfaceView GL_SurfaceView TextureView SurfaceTexture View
  • 6. What is a SurfaceView? A View which gives you access to a Surface using .getHolder(), which is drawn on a seperate thread and is double/triple buffered behind the scenes. It cuts holes and displays underneath the window it is in.
  • 7. How to use it: • Setup • Threads vs Runnables and other control mechanisms • Loops • UI communication • Tips
  • 9. Setup: Activity and View @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // set flags as needed getWindow().setFormat(PixelFormat.RGBA_8888); this.setVolumeControlStream(AudioManager.STREAM_MUSIC); setContentView(R.layout.activity_game); // get handles to the View from XML, and its Thread mCSurfaceView = (MySurfaceView) findViewById(R.id.surfaceview); setSurfaceType(View.LAYER_TYPE_SOFTWARE); mSurfaceViewThread = mSurfaceView.getThread(); createInputObjectPool();
  • 10. Your SurfaceView class public class ChiBlastSurfaceView extends SurfaceView implements SurfaceHolder.Callback { public ChiBlastSurfaceView(Context context) { super(context); mSurfaceCreated = false; touchBool = true; // register our interest in hearing about changes to our surface SurfaceHolder holder = getHolder(); holder.addCallback(this); myHandler = new MyInnerHandler(this); // create thread only; it's started in surfaceCreated() thread = new ChiBlastSurfaceViewThread(holder, context, myHandler); setFocusable(true); // make sure we get key events }
  • 11. Your SurfaceView callbacks 1/3 SurfaceHolder.Callback: @Override public void surfaceCreated(SurfaceHolder holder) { // start the thread here so that we don't busy-wait in run() waiting for the surface to be created if (mSurfaceCreated == false) { createThread(holder); mSurfaceCreated = true; touchBool = true; } }
  • 12. Your SurfaceView callbacks 2/3 SurfaceHolder.Callback: @Override public void surfaceDestroyed(SurfaceHolder holder) { mSurfaceCreated = false; cleanupResource(); terminateThread(); }
  • 13. Your SurfaceView callbacks 3/3 SurfaceHolder.Callback: @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { thread.setSurfaceSize(width, height); }
  • 14. Setup: driving the SurfaceView Runnables, thread and loops, oh my!
  • 15. Setup: Thread public class ChiBlastSurfaceViewThread extends Thread { public ChiBlastSurfaceViewThread(SurfaceHolder surfaceHolder, Context context, Handler handler) { // get handles to some important objects mSurfaceHolder = surfaceHolder; mSurfaceHolder.setFormat(PixelFormat.RGBA_8888); mContext = context; res = context.getResources(); //any other initialization: ops = new BitmapFactory.Options(); ops.inPurgeable = true; ops.inDensity = 0; ops.inDither = false; ops.inScaled = false; ops.inPreferredConfig = Bitmap.Config.ARGB_8888; ops.inJustDecodeBounds = false; }
  • 16. @Override public void run() { while (mRun) { Canvas c = null; try { // update game state processInput(); //if (mMode == STATE_SCROLL_MAP) if (mMode != STATE_PAUSE) { updatePhysics(timeDiff); } c = mSurfaceHolder.lockCanvas(null); synchronized (mSurfaceHolder) { doDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } } Setup: Thread
  • 17. The Thread and your Activity What does this now mean for your Activity? or How do we make this fit into the Android Lifecycle?
  • 18. The Thread and your Activity @Override protected void onPause() { super.onPause(); // pause game when Activity pauses mSurfaceView.getThread().pause(); mSurfaceView.terminateThread(); System.gc(); }
  • 19. The Thread and your Activity @Override protected void onResume() { super.onResume(); if (mSurfaceView.mSurfaceCreated) { mSurfaceView.createThread(mSurfaceView.getHolder()); setSurfaceType(View.LAYER_TYPE_SOFTWARE); } mSurfaceView.SetTouch(true); }
  • 20. The Thread and your Activity @Override protected void onRestoreInstanceState(Bundle inState) { // just have the View's thread load its state from our Bundle if (mSurfaceView.mSurfaceCreated) { mSurfaceView.createThread(mSurfaceView.getHolder()); setSurfaceType(View.LAYER_TYPE_SOFTWARE); } mSurfaceViewThread.restoreState(inState); }
  • 21. The main loop • AFAFP • Fixed step
  • 22. @Override public void run() { long beginTime; // the time when the cycle begun long timeDiff; // the time it took for the cycle to execute int sleepTime; // ms to sleep (<0 if we're behind) int framesSkipped; // number of frames being skipped timeDiff = System.currentTimeMillis()+50; sleepTime = 0; while (mRun) { Canvas c = null; try { beginTime = System.currentTimeMillis(); framesSkipped = 0; // resetting the frames skipped // update game state processInput(); //if (mMode == STATE_SCROLL_MAP) if (mMode != STATE_PAUSE) { updatePhysics(timeDiff); } c = mSurfaceHolder.lockCanvas(null); synchronized (mSurfaceHolder) { doDraw(c); } The main loop 1/3
  • 23. The main loop 2/3 // calculate how long did the cycle take timeDiff = System.currentTimeMillis() - beginTime; // calculate sleep time sleepTime = (int)(FRAME_PERIOD - timeDiff); if (sleepTime > 0) { // if sleepTime > 0 we're OK try { // send the thread to sleep for a short period // very useful for battery saving Thread.sleep(sleepTime); } catch (InterruptedException e) {} } while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) { // we need to catch up // update without rendering processInput(); if (mMode != STATE_PAUSE) { updatePhysics(timeDiff); } // add frame period to check if in next frame sleepTime += FRAME_PERIOD; framesSkipped++; }
  • 24. The main loop 3/3 } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } }
  • 26. static class MyInnerHandler extends Handler { private final WeakReference<ChiBlastSurfaceView> mView; MyInnerHandler(ChiBlastSurfaceView aView) { mView = new WeakReference<ChiBlastSurfaceView>(aView); } @Override public void handleMessage(Message m) { ChiBlastSurfaceView theView = mView.get(); theView.mStatusText.setText(m.getData().getString("text")); if (m.getData().getInt("viz") == View.VISIBLE) { theView.mStatusText.setVisibility(View.VISIBLE); //mStatusText.setAnimation(displayTextAnim); //mStatusText.startAnimation(displayTextAnim); } else { if (m.getData().getInt("viz") == View.INVISIBLE) { theView.mStatusText.setVisibility(View.INVISIBLE); theView.mStatusText.setAnimation(null); } else if (m.getData().getInt("viz") == View.GONE) { theView.mStatusText.setVisibility(View.GONE); } } theView.mStatusText.invalidate(); } }
  • 27. Setup: Cleanup public void terminateThread () { boolean retry = true; thread.setRunning(false); while (retry) { try { thread.join(); retry = false; } catch (InterruptedException e) { } //break; //THIS BREAKS IT ON PUSHING HOME } //thread = null; //THIS BREAKS IT ON PUSHING HOME }
  • 28. Tips • Input buffer • Object creation • Scaling • Drawing, bitmaps and other dirty things
  • 29. Tips: input buffer in SVActivity private void createInputObjectPool() { inputObjectPool = new ArrayBlockingQueue<InputObject>(INPUT_QUEUE_SIZE); for (int i = 0; i < INPUT_QUEUE_SIZE; i++) { inputObjectPool.add(new InputObject(inputObjectPool)); } }
  • 30. public class InputObject { public static final byte EVENT_TYPE_KEY = 1; public static final byte EVENT_TYPE_TOUCH = 2; public static final int ACTION_KEY_DOWN = 1; public static final int ACTION_KEY_UP = 2; public static final int ACTION_TOUCH_DOWN = MotionEvent.ACTION_DOWN; public static final int ACTION_TOUCH_POINTER_DOWN = MotionEvent.ACTION_POINTER_DOWN; //public static final int ACTION_TOUCH_POINTER_2_DOWN = MotionEvent.ACTION_POINTER_2_DOWN; public static final int ACTION_TOUCH_MOVE = MotionEvent.ACTION_MOVE; public static final int ACTION_TOUCH_UP = MotionEvent.ACTION_UP; public static final int ACTION_TOUCH_POINTER_UP = MotionEvent.ACTION_POINTER_UP; //public static final int ACTION_TOUCH_POINTER_2_UP = MotionEvent.ACTION_POINTER_2_UP; public ArrayBlockingQueue<InputObject> pool; public byte eventType; public long time; public int action; public int keyCode; public int x; public int y; public int x2; public int y2; public int pointerID; public int pointerIndex; public int pointerIndex2; InputObject 1/5
  • 31. InputObject 2/5 public InputObject(ArrayBlockingQueue<InputObject> pool) { this.pool = pool; } public void useEvent(KeyEvent event) { eventType = EVENT_TYPE_KEY; int a = event.getAction(); switch (a) { case KeyEvent.ACTION_DOWN: action = ACTION_KEY_DOWN; break; case KeyEvent.ACTION_UP: action = ACTION_KEY_UP; break; default: action = 0; } time = event.getEventTime(); keyCode = event.getKeyCode(); }
  • 32. public void useEvent(MotionEvent event) { eventType = EVENT_TYPE_TOUCH; int a = event.getAction(); switch (a) { case MotionEvent.ACTION_DOWN: action = ACTION_TOUCH_DOWN; break; case MotionEvent.ACTION_POINTER_DOWN: action = ACTION_TOUCH_POINTER_DOWN; break; case MotionEvent.ACTION_POINTER_2_DOWN: action = ACTION_TOUCH_POINTER_DOWN; break; case MotionEvent.ACTION_MOVE: action = ACTION_TOUCH_MOVE; break; case MotionEvent.ACTION_UP: action = ACTION_TOUCH_UP; break; case MotionEvent.ACTION_POINTER_UP: action = ACTION_TOUCH_POINTER_UP; break; case MotionEvent.ACTION_POINTER_2_UP: action = ACTION_TOUCH_POINTER_UP; break; default: action = -1; } InputObject 3/5
  • 33. InputObject 4/5 time = event.getEventTime(); pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT; pointerID = event.getPointerId(pointerIndex); x = (int) event.getX(pointerIndex); y = (int) event.getY(pointerIndex); if (event.getPointerCount() > 1) { pointerIndex2 = pointerIndex== 0 ? 1 : 0; x2 = (int)event.getX(pointerIndex2); y2 = (int)event.getY(pointerIndex2); } }
  • 34. InputObject 5/5 public void useEventHistory(MotionEvent event, int historyItem) { eventType = EVENT_TYPE_TOUCH; action = ACTION_TOUCH_MOVE; time = event.getHistoricalEventTime(historyItem); pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT; pointerID = event.getPointerId(pointerIndex); x = (int) event.getHistoricalX(pointerIndex, historyItem); y = (int) event.getHistoricalY(pointerIndex, historyItem); if (event.getPointerCount() > 1) { pointerIndex2 = pointerIndex== 0 ? 1 : 0; x2 = (int) event.getHistoricalX(pointerIndex2, historyItem); y2 = (int) event.getHistoricalY(pointerIndex2, historyItem); } } public void returnToPool() { pool.add(this); }
  • 35. @Override public boolean onTouchEvent(MotionEvent event) { try { // history first int hist = event.getHistorySize(); if (hist > 0) { // add from oldest to newest for (int i = 0; i < hist; i++) { //for (int i = hist-1; i > -1; i--) { InputObject input = inputObjectPool.take(); input.useEventHistory(event, i); mSurfaceViewThread.feedInput(input); } } // current last InputObject input = inputObjectPool.take(); input.useEvent(event); mSurfaceViewThread.feedInput(input); } catch (InterruptedException e) { } // don't allow more than 60 motion events per second try { Thread.sleep(16); } catch (InterruptedException e) { } return true; } Back to the activity:
  • 36. public void feedInput(InputObject input) { synchronized(inputQueueMutex) { try { inputQueue.put(input); } catch (InterruptedException e) { //Log.e(TAG, e.getMessage(), e); } } } private void processInput() { synchronized(inputQueueMutex) { ArrayBlockingQueue<InputObject> inputQueue = ChiBlastSurfaceView.inputQueue; while (!inputQueue.isEmpty()) { try { InputObject input = inputQueue.take(); if (input.eventType == InputObject.EVENT_TYPE_KEY) { //processKeyEvent(input); } else if (input.eventType == InputObject.EVENT_TYPE_TOUCH) { processMotionEvent(input); } input.returnToPool(); } catch (InterruptedException e) { //Log.e(TAG, e.getMessage(), e); } } } } And in the SurfaceView.Thread:
  • 38. Tips: object creation Just don’t do it.
  • 39. Tips: object creation Or do it up front. No matter how odd that sometimes may seem.
  • 40. Tips: scaling Two types of scaling: • Realtime whole view SV scaling only works from Android N • Fixed scaling (as done in Unreal Tournament 3)
  • 41. Tips: scaling @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { //thread.setSurfaceSize(width, height); if (mCanvasWidth != width) { int scaledWidth = (int)(width*0.75f); int scaledHeight = (int)(height*0.75f); if (scaledHeight != height) { yRatio = (float)(scaledHeight / (float)height); xRatio = (float)(scaledWidth / (float)width); } holder.setFixedSize(scaledWidth, scaledHeight); thread.setSurfaceSize(scaledWidth, scaledHeight); } }
  • 42. Tips: drawing, bitmaps and other dirty things
  • 43. Tips: drawing, bitmaps and other dirty things In SurfaceView.Thread doDraw(): canvas.drawBitmap(mBackgroundImage, null, fullscreenRect, mPicPaint);
  • 44. Q&A