Coding advice on Buttons and MediaPlayer (avoid choreographer skipped frames) - android

I've been making a soundboard app of my infant son's sounds. The app simply opens to MainActivity class where it displays a scrollable list of buttons that, when pressed, will playback a pre-recorded MP3 of my son screaming something.
Once it's debugging or running on my Nexus 7 there is a Choreographer error and somewhere between 50 and 90 frames are skipped. The warning is that the 'application may be doing too much work on its main thread'. On my device three random buttons refuse to play sound until the app is closed and re-opened but then another three random buttons refuse to work. This problem didn't occur when my app only had eight buttons in a simple LinearLayout (without ScrollView).
I'm a beginner to both java and Android programming but, from what I guess, my laughably poor code is taking too much memory to properly function. My question is either, how would you code a scrollable list of 33 buttons that play their own sound, or could someone teach me how to better my code, please?
MainActivity.java (showing only the first three buttons/sounds for this question):
import android.media.MediaPlayer;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.Button;
import android.view.View;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MediaPlayer ahh = MediaPlayer.create(this, R.raw.ahh);
final MediaPlayer dededeh = MediaPlayer.create(this, R.raw.dededeh);
final MediaPlayer neganegabunbunbug = MediaPlayer.create(this, R.raw.naganagbunbun);
Button bahh = (Button) this.findViewById(R.id.Ahhhbutton);
bahh.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ahh.start();
}
});
}
}
And the activity_main.xml (again with only the first three buttons for this question):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/tab1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin" >
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:id="#+id/Ahhhbutton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:background="#layout/button_shape"
android:text="#string/button1a"
android:textColor="#d1d1d1"
android:textSize="20sp" />
<Button
android:id="#+id/dededehbutton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:background="#layout/button_shape"
android:text="#string/button1b"
android:textColor="#d1d1d1"
android:textSize="20sp" />
<Button
android:id="#+id/neganegabunbunbugbutton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:background="#layout/button_shape"
android:text="#string/button1c"
android:textColor="#d1d1d1"
android:textSize="20sp" />
Button bdededeh = (Button) this.findViewById(R.id.dededehbutton);
bdededeh.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dededeh.start();
}
});
Button bnaganagbunbun = (Button) this.findViewById(R.id.naganagabunbutton);
bnaganagbunbun.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
neganegabunbunbug.start();
}
});
</LinearLayout>
</ScrollView>
</LinearLayout>

It's not the layout. As your error said, you shouldn't do any long-term work on the Main-Thread.
The main-thread (or on Android the UI-Thread) handles interaction with the user. For the UI to not freeze and feel fast, you should execute long-term actions off the UI-Thread.
Also, the problem is not the actual playback, but you loading all media-files:
MediaPlayer.create(this, R.raw.ahh);
As the docs for the create()-method suggest:
[...] When done with the MediaPlayer, you should call release(), to
free the resources. If not released, too many MediaPlayer instances
will result in an exception.
For the "off the UI-Thread"-thing, you have three options:
Directly can create a Runnable-implementing class that plays your sound and submit it to a ScheduledThreadPoolExecuter (maximum control, more code).
Use an AsyncTask (Android exclusive) to play the song (fairly simple).
If you want the sound-playback to continue even when the app is not currently in the foreground, use a Service (most work)
So, what you should do is:
Store a List of your raw-file references (the R.raw.ahh) and don't load them directly.
When a song is selected for playback, call create() on that songs ID and start playback off the UI-Thread.
When done, call release() to release the resources.

Related

Can Android layout be loaded in the C language?

This link says that to make the app display a layout, you create a main_layout.xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button" />
</LinearLayout>
and then load it in onCreate method:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// main_layout.xml is compiled into the R.layout.main_layout object
setContentView(R.layout.main_layout);
}
My problem is that I am coding in C, and have the C version of "onCreate" method:
JNIEXPORT
void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState,
size_t savedStateSize) {
....
}
is it possible to load the layout and make the app display it in the C language?
C uses "ANativeActivity" that it doesn't has any of Java methods and resources/objects. Even if you manually parse that XML you should MANUALLY implement all Widgets/Components in C.....so it's near impossible due to high amount of work involved in it.
Usually someone chooses C on Android to do "some special work" that is not available on normal Java or due to performance issues on it.

Set-and-swipe Forms using Android Fragments

I am trying to develop a very simple Android app for rapid data collection, where an activity displays a fragment containing a form with a number of RadioGroup elements. I would like to have the user be able to quickly tap out the radio selections, then be able to swipe the form to the right to dispose of the fragment, record the data, and bring up another empty form.
What I can't figure out is how to set up the "swipe and replace" feature. I'm thinking of something similar to the ViewPager support class, except no scrolling backwards and an unlimited number of pages. What classes can I use to achieve this kind of behavior? Here's my stripped-down code:
fragment_record.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" >
<LinearLayout
android:id="#+id/mainLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Group 1"
android:id="#+id/textView" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="#+id/Group1">
<!-- Radio Buttons /-->
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Group 2"
android:id="#+id/textView2" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="#+id/Group2">
<!-- Radio Buttons /-->
</RadioGroup>
</LinearLayout>
</ScrollView>
RecordFragment.java:
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class RecordFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_record, container, false);
return view;
}
}
activity_record.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container" >
</FrameLayout>
RecordActivity.java
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.View;
public class RecordActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record);
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) return;
RecordFragment theFragment = new RecordFragment();
theFragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(R.id.fragment_container, theFragment).commit();
}
}
}
Ok, the simple answer to this was delivered obiter dicta in the tech talk a month or two ago where Google showed the preview of Android Studio 2.0. One of the presenters said 'we see a lot of people out there using Fragments in places where they should be using ViewGroups.' I was one of those people too.
A wizard is a complex problem: you are usually building something incrementally, so there is state, dependencies, and flow. It's hard to make really generic because what is being built is completely different. Also, it's a clear anti pattern to partially construct the target object leaving a bunch of nullable fields to be filled in as the process grinds on.
If each group can contain a part, then you can assemble the parts and build the target from the parts. There's no shame in it if those parts are not necessarily organic domain objects: if you are modeling the process of obtaining a job, there can be an Application (the thing the user has to fill out) and the CV or Submission (what is built in the form to represent a specific request for consideration).

Animated backround for my android activity using Eclipse and ADT

I want to create a background that covers the whole screen of my Activity.
Just like:
android:background="#drawable/background"
But I want this background to be animated (60 frames), looping and target as many device resolutions as possible.
I tried to do a drawble .jpg sequence, but the problem is that I use 1920x1080, and the logcat while trying to test it on the emulator, the application crashes, and LogCat gives me en error saying, not enough memory
Then I tried to import a video of my sequence and set it to play on the background. But I don't know where to put the video in my res folder and call it from there with SetVideoPath(), in order to play it.
What is the best approach for an animated background, that covers multiple devices with different resolutions?
#Arun C Thomas
I still get an error in logcat,
OutOfMemoryError
my code is this:
package combiolab.biolab;
import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Bundle;
import android.view.WindowManager;
public class MainActivity extends Activity {
private Renderer graphicsRenderer;
//public static String gl;
AnimationDrawable anim;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
GLSurfaceView myGLSurfaceView = (GLSurfaceView) findViewById(R.id.gl);
myGLSurfaceView.setEGLConfigChooser(true);
myGLSurfaceView.setRenderer(graphicsRenderer);
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<android.opengl.GLSurfaceView
android:id="#+id/gl"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</android.opengl.GLSurfaceView>
</RelativeLayout>
You need to create an OpenGL view,
then add it like
<android.opengl.GLSurfaceView
android:id="#+id/gl"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</android.opengl.GLSurfaceView>
In onCreate() implement Renderer like
GLSurfaceView myGLSurfaceView = (GLSurfaceView) findViewById(R.id.gl);
myGLSurfaceView.setEGLConfigChooser(true);
myGLSurfaceView.setRenderer(graphicsRenderer);

Easiest way to have button open browser to specific URL

It's a simple app I've got and I'd like the button I've made to launch a specific URL via the browser. Could you guys give me a little info to get this going, like I've said I've got the button already to go in my app. Here's the code -- lemme' know if you need anything else
.java File
package reseeveBeta.mpi.dcasey;
import android.app.Activity;
import android.os.Bundle;
public class ReseeveBetaActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
.XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Welcome to Reseeve, tap register to begin account creation" />
<Button
android:id="#+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Register" />
<EditText
android:id="#+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textMultiLine"
android:text="If you already have and account, please login below" >
<requestFocus />
</EditText>
</LinearLayout>
This line should open your built-in browser, with the specified url:
startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("http://www.google.com")));
Your Activity should have parts like this:
//define class variables here
Button btn;
protected void onCreate(Bundle savedInstanceState)
{
//some code of yours
btn=(Button)findViewById(R.id.button1);
btn.setOnClickListener(this);
//more code of yours
}
//whatever else you have in your source code
public void onClick(View v)
{
//handle the click events here, in this case open www.google.com with the default browser
startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("http://www.google.com")));
}
It might not be 100% accurate syntax, since I did just write this on my own, but you get the idea.
You can do that with Rebol 3, this easily:
REBOL []
load-gui
view [button "Go" on-action [browse http://msn.com]]
That's a fully functioning GUI program, which runs on Android AND on desktop, using the exact same code across all platforms. Take a look at:
http://rebolforum.com/index.cgi?f=printtopic&permalink=Nick25-Aug-2013/10:08:38-7:00&archiveflag=new
Simple create one WebView in xml
<WebView
android:id="#+id/web_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0" />
Here is the Simple java code for that
String URL="www.gtumca.co.cc";
WebView wv=(WebView)findViewById(R.layout.web_view);
onClick()
{
wv.loadUrl(URL);
}

Screen blinking when using a webview with flash

Edit: I made a demo apk, so you can understand what I mean: http://cl.ly/3g0s1p030j243y0p3m2F
For my application, I want a kind of "Super Power Point", or a keynote (the commercial team will present the product to their customers) using all the Android goodness, gestures, etc... on an Android tablet. As Honeycomb is not yet ready and because we need it before march, we choose some random Froyo Tablet (Archos 101), but my issue is for every tablet/phone I tried.
I made a really great application, but for some animations during the presentation, the customer wanted to use flash animations. Because I couldn't code animations (sort of little movies/ animated graphics) that easily in Android and the lack of time, that seemed to be a good idea.
So, after some search on the Web, I used webview and this code:
WebView mWebView1 = (WebView) findViewById(R.id.webview1);
mWebView1.getSettings().setJavaScriptEnabled(true);
mWebView1.getSettings().setPluginsEnabled(true);
mWebView1.loadUrl("file:///android_asset/graph_01.swf");
This work pretty well, but on every device I tried (Archos 101, Nexus One, Nexus S, Galaxy S, Xperia, Desire, HTC Hero, and really more) every activity with a webview blink, a few milliseconds of black screen, then the animation finally appear.
PS: My layout is quite simple too:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<WebView android:id="#+id/webview1" android:layout_height="fill_parent"
android:layout_width="fill_parent"></WebView>
</LinearLayout>
Please help me, I cannot imagine I am the only one to face this issue.
Thank a lot for any help. You have all my code and the demo apk.
THIS ANSWER ADDED LATER:
So the problem is that Adobe's Flash Player creates a SurfaceView inside the WebView and there is a delay between the SurfaceView appearing and the Flash engine bothering to draw anything. In that small gap the SurfaceView appears totally black.
The solution I've found is to subclass WebView and add a customized SurfaceView to the view tree. Secondly it is also important to suppress the first draw() call to the Adobe view. The code for my subclassed WebView is as follows:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.webkit.WebView;
import android.widget.AbsoluteLayout;
public class FixAdobeWebView extends WebView {
View whiteView;
private boolean eatenFirstFlashDraw;
public FixAdobeWebView(Context context, AttributeSet attrs) {
super(context, attrs);
whiteView = new WhiteSurfaceView(context);
whiteView.setLayoutParams(new AbsoluteLayout.LayoutParams(800, 480, 0, 0));
addView(whiteView);
}
private class WhiteSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
public WhiteSurfaceView(Context context) {
super(context);
getHolder().addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.WHITE);
holder.unlockCanvasAndPost(canvas);
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) { }
#Override
public void surfaceDestroyed(SurfaceHolder holder) { }
}
//
// Override drawChild to eat the first draw of the FlashPaintSurface
//
#Override
protected boolean drawChild (Canvas canvas, View child, long drawingTime) {
if (!eatenFirstFlashDraw && child.getClass().getName().equals("com.adobe.flashplayer.FlashPaintSurface")) {
eatenFirstFlashDraw = true;
return true;
}
return super.drawChild(canvas, child, drawingTime);
}
}
The layout XML should declare an instance of this class, i.e. :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/white"
>
<org.test.FixAdobeWebView android:id="#+id/webview1"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
/>
</LinearLayout>
On my Nexus One running CM6.1 this works perfectly in that the WebView appears as solid white until the Flash SurfaceView starts drawing. You will probably need to adapt it a bit, e.g. get rid of the hardcoded 800x480 dimensions, and also destroy the white SurfaceView once it's no longer needed.
ORIGINAL (WRONG) ANSWER:
Looks like Adobe are to blame for this one.
The least awful workaround I've found so far is to :
Make the background of the WebView's parent a compatible colour (i.e. white).
Hide the WebView initially.
Wait until you get the page loaded event, and then queue a runnable to show the WebView after a one second delay.
It means you get a guaranteed one second delay before you can see anything, but it's less jarring than that horrible black flash afaik.
Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/white"
>
<WebView android:id="#+id/webview1"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:visibility="invisible"
/>
</LinearLayout>
Code:
final Handler handler = new Handler();
...
final WebView mWebView1 = (WebView) findViewById(R.id.webview1);
mWebView1.getSettings().setJavaScriptEnabled(true);
mWebView1.getSettings().setPluginsEnabled(true);
mWebView1.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
mWebView1.setVisibility(View.VISIBLE);
}
}, 1000);
}
});
mWebView1.loadUrl("file:///android_asset/graph_01.swf");
There might be a better way though that would remove the need for a fixed delay...
It's been a while since I did anything with Flash but I seem to recall there is a way for the Flash control to use the browser's Javascript engine (a quick Google turns up the Flex Ajax Bridge which seems likely).
What you could do is, with the aforementioned bridge, use ActionScript to call a Javascript function which does an alert(). In your Java code you'd hook your mWebView1 up to a WebChromeClient which implements onJsAlert(). In that function you'd make the WebView visible.

Categories

Resources