I’ve got a fairly simple app I wrote to sharpen my JNI and multi-threading skills which takes either a string of ascii characters to convert to a binary string or a binary character string to convert to an ascii character string. The math behind each conversion is processed via a native lib, and each call to the native code runs on its own thread using AsyncTask so as not to disrupt the UI. The trouble is that whenever onLayout is called when the UI widgets need to change size or position, the new image is placed over top of the still visible old image rather than replacing it. I’ve tried removing likely culprits (the JNI module, the AsyncTask module) but even when its functionality is stripped and only the UI remains I see the same result of new UI views overlapping instead of replacing older views. As you can see from my code below, I’ve also variably tried the old setContentView(…) approach to UI hierarchy modeling in addition to the more flexible custom addView model I have active now, but the results were still the same. NOTE: This only happens on device (Nexus 7 running latest 4.2.1 and Nexus S running 4.0.2); in the emulator everything works fine. This would seem to implicate GLES invocations, but I don’t make any of my own in this app and obviously this isn’t happening to every app on device so it doesn’t seem like an OS issue…
Activity Class:
package cresco.ai.asciitobinstring.core;
import android.app.Activity;
import android.os.Bundle;
public class AsciiToBinActivity_Minimalist extends Activity{
private AsciiToBin_Main hMain;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
hMain = new AsciiToBin_Main(this);
hMain.init();
}
@Override
protected void onResume(){
super.onResume();
}
@Override
protected void onPause(){
super.onPause();
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState){
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onRestart(){
super.onRestart();
}
@Override
protected void onDestroy(){
super.onDestroy();
}
}
Main manager class:
package cresco.ai.asciitobinstring.core;
import cresco.ai.asciitobinstring.gui.ConversionViewController;
import cresco.ai.asciitobinstring.gui.DefineLayoutParams;
import cresco.ai.asciitobinstring.gui.TutorialViewController;
import cresco.ai.asciitobinstring.math.StringConverter;
import android.app.Activity;
import android.content.res.AssetManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.ViewFlipper;
public class AsciiToBin_Main {
private Activity hAct;
private View mvMainView;
private ViewFlipper mvRootViewFlipper;
private ConversionViewController mvConversionViewController;
private TutorialViewController mvTutorialViewController;
private RelativeLayout mvRootRL;
private AssetManager mvAssetManager;
private LayoutInflater mvInflater;
private StringConverter mvConverter;
public AsciiToBin_Main(Activity act){
hAct = act;
mvConverter = new StringConverter(this);
mvRootRL = new RelativeLayout(hAct);
hAct.addContentView(mvRootRL,
DefineLayoutParams.getParams(DefineLayoutParams.getMM()));
//hAct.setContentView(mvRootRL);
///hAct.setContentView(R.layout.conversion_layout);
}
public void init(){
//Add the viewgroup subclass viewflipper to the rootRL
mvRootViewFlipper = new ViewFlipper(hAct);
mvRootRL.addView(mvRootViewFlipper);
//Instantiate our conversion view controller
mvConversionViewController = new ConversionViewController(this);
mvTutorialViewController = new TutorialViewController(this);
//Fire up the conversion view
mvConversionViewController.initGUI();
mvConversionViewController.initSpinnerElements();
//Fire up the tutorial view
mvTutorialViewController.initGUI();
}
public RelativeLayout getRootRL(){
return mvRootRL;
}
public ViewFlipper getRootViewFlipper(){
return mvRootViewFlipper;
}
public View getMainView(){
return mvMainView;
}
public Activity getAct(){
return hAct;
}
public void setMainView(View v){
mvMainView = v;
}
public void setActivity(Activity a){
hAct = a;
}
public StringConverter getConverter(){
return mvConverter;
}
public void setConverter(StringConverter sc){
mvConverter = sc;
}
}
Conversion View Controller:
package cresco.ai.asciitobinstring.gui;
import cresco.ai.asciitobinstring.core.AsciiToBin_Main;
import cresco.ai.asciitobinstring.core.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.os.AsyncTask;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
public class ConversionViewController extends View implements OnClickListener,
OnItemSelectedListener{
private AsciiToBin_Main hMain;
private LayoutInflater mvInflater;
private View mvConversionView;
private Button mvConvertB;
private Button mvToTutorialB;
private Spinner mvConversionTypeSP;
private EditText mvAsciiStringET;
private EditText mvBinStringET;
private ArrayAdapter<CharSequence> mvSpinnerAdapter;
public ConversionViewController(AsciiToBin_Main main) {
super(main.getAct());
hMain = main;
}
public void initGUI(){
mvInflater = (LayoutInflater)
hMain.getAct().getSystemService(hMain.getAct().LAYOUT_INFLATER_SERVICE);
mvConversionView = mvInflater.inflate(R.layout.conversion_layout,
hMain.getRootViewFlipper(),false);
hMain.getRootViewFlipper().addView(mvConversionView);
//hMain.getRootRL().addView(mvConversionView);
mvConvertB = (Button)hMain.getAct().findViewById(R.id.convertB);
mvToTutorialB =
(Button)hMain.getAct().findViewById(R.id.conversion_To_Tutorial_B);
mvAsciiStringET =
(EditText)hMain.getAct().findViewById(R.id.asciiStringET);
mvBinStringET = (EditText)hMain.getAct().findViewById(R.id.binStringET);
mvConversionTypeSP =
(Spinner)hMain.getAct().findViewById(R.id.conversionTypeSP);
mvConvertB.setOnClickListener(this);
mvToTutorialB.setOnClickListener(this);
mvConversionTypeSP.setOnItemSelectedListener(this);
}
public void initSpinnerElements(){
mvSpinnerAdapter = ArrayAdapter.createFromResource(hMain.getAct(),
R.array.conversion_choices_array, android.R.layout.simple_spinner_item);
mvSpinnerAdapter.setDropDownViewResource
(android.R.layout.simple_spinner_dropdown_item);
mvConversionTypeSP.setAdapter(mvSpinnerAdapter);
mvConversionTypeSP.setSelection(0);
//Now that that the UI is ready, display it
////hMain.getRootViewFlipper().showNext();
}
@Override
protected void onDraw(Canvas c){
super.onDraw(c);
}
@Override
public void onClick(View v) {
if(v==mvConvertB){
Log.d("me", "button pressed! Conversion type selected currently is
"+mvSpinnerAdapter.getItem(mvConversionTypeSP.getSelectedItemPosition()));
if(mvConversionTypeSP.getSelectedItemPosition() == 0){
//This is the convert ascii to binary string choice
//Calls the native method calculateBinFromAsciiStringJNI
//Not a terribly heavy process, but since it is disparate
//from
//the UI we should probably grant it its own thread...
//Uncomment when UI overlay bug is solved
new
NativeStringConversionTask(0).execute(mvAsciiStringET.getText().toString());
//mvBinStringET.setText(hMain.getConverter().
//calculateBinFromAsciiStringJNI(mvAsciiStringET
//.getText().toString())); //all on the UI thread, not good
}
else if(mvConversionTypeSP.getSelectedItemPosition() == 1){
//This is the convert binary to ascii string choice
//Uncomment when UI overlay bug is solved
new
NativeStringConversionTask(1).execute(mvBinStringET.getText().toString());
//mvAsciiStringET.setText(hMain.getConverter().
//calculateAsciiFromBinStringJNI(mvBinStringET
//.getText().toString())); //all on the UI thread. Not good
}
}
else if(v==mvToTutorialB){
//hMain.getRootViewFlipper().setDisplayedChild(1);
hMain.getRootViewFlipper().showNext();
//hMain.getAct().setContentView(R.layout.tutorial_layout);
}
}
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
Log.d("me", "itemselectedlistener received a callback! the position
selected is "+arg2);
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
Log.d("me", "itemselectedlistener received a no items slected callaback");
}
//Uncomment once the UI overlay issue is solved
private class NativeStringConversionTask extends AsyncTask<String,Integer,String>{
private int taskID = 0;
public NativeStringConversionTask(int id){
taskID = id;
}
@Override
protected void onPreExecute(){
Log.d("me","in the preExecute of the nativeStringConversion Async
task");
}
@Override
protected String doInBackground(String... s) {
//NB: if we wanted onProgressUpdate to be called with something we
//would
//invoke publishProgress(something) in this function
Log.d("me", "bg task started with taskID "+taskID);
if(taskID == 0){
publishProgress(taskID);
return
hMain.getConverter().calculateBinFromAsciiStringJNI(s[0].toString());
}
else if (taskID == 1){
publishProgress(taskID);
return
hMain.getConverter().calculateAsciiFromBinStringJNI(s[0].toString());
}
else{
publishProgress(taskID);
return "This shouldn't appear. if it does... may the
gods below help us all";
}
}
@Override
protected void onProgressUpdate(Integer... progress){
Log.d("me", "progressUpdate called!");
}
@Override
protected void onPostExecute(String result){
Log.d("me", "calling postExecute...");
if(taskID == 0){
mvBinStringET.setText((String)result);
}
else if (taskID == 1){
mvAsciiStringET.setText((String)result);
}
else{
mvBinStringET.setText("This shouldn't appear. if it
does... may the gods below help us all");
mvAsciiStringET.setText("This shouldn't appear. if it
does... may the gods below help us all");
}
//mvBinStringET.setText(s);
}
}
}
Tutorial View Controller:
package cresco.ai.asciitobinstring.gui;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import cresco.ai.asciitobinstring.core.AsciiToBin_Main;
import cresco.ai.asciitobinstring.core.R;
public class TutorialViewController extends View implements OnClickListener{
private AsciiToBin_Main hMain;
private LayoutInflater mvInflater;
private View mvTutorialView;
private Button mvToConversionB;
public TutorialViewController(AsciiToBin_Main main){
super(main.getAct());
hMain = main;
}
public void initGUI(){
mvInflater = (LayoutInflater)
hMain.getAct().getSystemService(hMain.getAct().LAYOUT_INFLATER_SERVICE);
mvTutorialView = mvInflater.inflate(R.layout.tutorial_layout,
hMain.getRootViewFlipper(),false);
hMain.getRootViewFlipper().addView(mvTutorialView);
mvToConversionB =
(Button)hMain.getAct().findViewById(R.id.tutorial_Conversion_B);
mvToConversionB.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if(v==mvToConversionB){
//hMain.getRootViewFlipper().setDisplayedChild(0);
hMain.getRootViewFlipper().showNext();
//hMain.getAct().setContentView(R.layout.conversion_layout);
}
}
}
StringConverter:
package cresco.ai.asciitobinstring.math;
import cresco.ai.asciitobinstring.core.AsciiToBin_Main;
public class StringConverter {
private AsciiToBin_Main hMain;
public StringConverter(AsciiToBin_Main main){
hMain = main;
}
//Uncomment once the UI overlay issue is solved
public native String calculateBinFromAsciiStringJNI(String s);
public native String calculateAsciiFromBinStringJNI(String s);
static {
System.loadLibrary("stlport_shared");
System.loadLibrary("ascii2bin");
}
}
conversion_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<EditText
android:id="@+id/asciiStringET"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/conversionTypeSP"
android:layout_centerHorizontal="true"
android:layout_marginTop="71dp"
android:hint="Your ASCII string goes here"
android:text="" />
<EditText
android:id="@+id/binStringET"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/asciiStringET"
android:layout_below="@+id/asciiStringET"
android:layout_marginTop="61dp"
android:hint="Your binary string goes here"
android:text="" />
<Button
android:id="@+id/convertB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/binStringET"
android:text="Convert Strings!" />
<Button
android:id="@+id/conversion_To_Tutorial_B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/convertB"
android:text="Read the tutorial on binary numbers" />
<Spinner
android:id="@+id/conversionTypeSP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
android:layout_marginTop="57dp" />
</RelativeLayout>
tutorial_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/tutorial_Conversion_B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="204dp"
android:text="Return to Conversion" />
</RelativeLayout>
Manifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cresco.ai.asciitobinstring.core"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="cresco.ai.asciitobinstring.core.AsciiToBinActivity_Minimalist"
android:label="@string/app_name"
android:theme="@style/FullscreenTheme" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The results I’m seeing are as follows:


Now what’s curious is that if I put the app in the background and then re-open it (so onPause and onResume are called) the UI that loads is refreshed properly, with old view artifacts removed. Of course attempting a new string conversion that resizes the edittext widgets creates the overlap again.

Does anyone know what might be behind this? I am targeting sdk revision 17, standard libs with a minSDK of 8. When I first saw this I thought it was a weird side-effect of the new-ish holo UI scheme, so I rolled back the styles.xml to a 2.3 compliant UI scheme but I didn’t see any improvement. could be that messed something up separately…
this isn’t really a good answer per se because it certainly doesn’t explain what happened in the first place, but I found that creating a new project from scratch fixed the odd errors I was seeing; therefore the problem was most likely caused by my mucking about with the UI styles… wish I had a more technical explanation than that, but at present I don’t. Anyway, if anyone else runs into this, re-creating your project seems to clear the problem. If anyone has more explanatory details they’d like to add, please feel free!