Table of Contents
Linux environment
How to set up a headless build environment.
OS | Ubuntu 20.04 |
Android SDK | Android 11 (API level 30) as of 26.11.2021 |
Install android-sdk on Ubuntu
# install openjdk8 - tested android sdk and its compatible sudo apt-get install openjdk-8-jdk # activate the 8th version if you have multiple using: # sudo update-alternatives --config java # install android-sdk under /usr/lib/android-sdk/ sudo apt update && sudo apt install android-sdk # download the commandlinetools and add them to sdk # <sdk>/cmdline-tools/latest/ wget -P /tmp/ https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip # check https://stackoverflow.com/questions/60440509/android-command-line-tools-sdkmanager-always-shows-warning-could-not-create-se sudo mkdir -p /usr/lib/android-sdk/cmdline-tools/ sudo unzip /tmp/commandlinetools-*.zip -d /usr/lib/android-sdk/cmdline-tools/ sudo mv /usr/lib/android-sdk/cmdline-tools/cmdline-tools/ /usr/lib/android-sdk/cmdline-tools/tools/ # open bashrc and modify the environment variables vim ~/.bashrc export ANDROID_HOME="/usr/lib/android-sdk/" export PATH=$PATH:$ANDROID_HOME/cmdline-tools/tools/bin:$ANDROID_HOME/platform-tools # apply changes in bashrc source ~/.bashrc # setting the rights sudo chown $USER:$USER $ANDROID_HOME -R # can successfully access the sdkmanager sdkmanager --version # install the platform tools https://developer.android.com/studio/releases/platform-tools sdkmanager "platform-tools" "platforms;android-29" # install the build tools https://developer.android.com/studio/releases/build-tools sdkmanager "build-tools;29.0.3" # confirm all the licenses for the android tools otherwise they wont work yes | sdkmanager --licenses # check for android sdk updates sdkmanager --update # you can build android project now cd <YOUR_ANDROID_PROJECT> ./gradlew build
HOWTO
Plan app's Layout
- use Fragments for top level elements which may be reused, since fragments can not be nested.
- implement Views with custom Layout, implemented in xml to use these blocks on deeper levels.
These views can be nested.
implement layout in xml and assign the xml layout to the view (see Create a View class which will wrap the XML Layout)
Declaring custom XML attributes
An in detail describtion about declaring custom XML attributes for custom views can be found here: http://stackoverflow.com/questions/2695646/declaring-a-custom-android-ui-element-using-xml
<fc #FF0000>ACHTUNG:</fc>
- Nutze diesen Namespace, statt den der eigenen App:
xmlns:app="http://schemas.android.com/apk/res-auto"
Fading Background
Is described here: http://nathanael.hevenet.com/android-dev-fading-background-animation/
ListView
Select Item
listView.setItemChecked(1, true);
Iterate Items
ContentListAdapter contentListAdapter = (ContentListAdapter) listView.getAdapter(); for(int i=0; i<contentListAdapter.getCount(); i++){ View v = (View) contentListAdapter.getItem(i); Log.d("View","Child is "+v );
Using JAR Libs
A Jar can be added to android, by putting it into the libs folder!
ACHTUNG:
Adroid kann nur JARs nutzen, die mit dem Compiler 1.7 gebaut wurden. Falls dies nicht der Fall ist - wird eine NoClassDefFound exception geworfen.
Tools
The tool monitor.bat can be used as a Standalone logger! Details are here
Aapt
Inspect the APK is done via
D:\Development\Eclipse Juno - Android\sdk\build-tools\android-4.2.2\aapt.exe dump badging theApp.apk
Unpackaging the APK is done via
apktool d HelloWorld.apk ./HelloWorld
Genimotion
Genimotion is a nice and fast Android VM.
It may support ARM Architecture, but this option must be enabled. How to do that is described here: http://forum.xda-developers.com/showthread.php?t=2528952
Maven
Dependncies
ACHTUNG: For some reason the dependencies form parent pom are not inherited to the child pom. So always add dependencies directly to children
Dependency to my own apklib:
The apklib
<artifactId>barcode-lib-commons-dataproject</artifactId> <name>BarcodeLibCommonsDataProject</name> <version>1.0.0</version> <packaging>apklib</packaging>
The dependency
<!-- Barcode Helper Lib --> <dependency> <groupId>de.ivu.fare.apps-13.2.PIROL</groupId> <artifactId>barcode-lib-commons-dataproject</artifactId> <version>1.0.0</version> <type>apklib</type> </dependency>
Package type
The apk is an App.
<artifactId>barcodelib2</artifactId> <version>1.0.0</version> <packaging>apk</packaging> <name>Barcode-Lib2</name>
The apklib - is the android lib project
<artifactId>barcodelib2</artifactId> <version>1.0.0</version> <packaging>apklib</packaging> <name>Barcode-Lib2</name>
Signieren
Wie man Maven einrichtet, um eine signierte APK bauen zu können steth hier:
http://www.simpligility.com/2010/07/sign-zipalign-and-to-market-to-market-with-maven/
Danach kann eine signierte apk z.B. durch erzeugt werden:
mvn clean install
ZipAlign
TO do the zipalign - use the code as described here: http://stackoverflow.com/questions/10915922/how-to-check-that-maven-goal-called-during-phase
DO not forget the <skip>false</skip>
<plugin> <groupId>com.jayway.maven.plugins.android.generation2</groupId> <artifactId>android-maven-plugin</artifactId> <version>3.2.0</version> <extensions>true</extensions> <configuration> <sdk> <platform>8</platform> </sdk> <emulator> <avd>2.3.3_API-10</avd> </emulator> <undeployBeforeDeploy>true</undeployBeforeDeploy> <assetsDirectory>${project.build.directory}/filtered-assets</assetsDirectory> <androidManifestFile>${project.build.directory}/filtered-manifest/AndroidManifest.xml</androidManifestFile> <zipalign> <skip>false</skip> <verbose>${build.verbosity}</verbose> <inputApk>${project.build.directory}/${project.artifactId}-${build.version.name}.apk</inputApk> <outputApk>${project.build.directory}/${project.artifactId}-${build.version.name}-aligned.apk</outputApk> </zipalign> </configuration> <executions> <execution> <id>zipalign</id> <phase>package</phase> <goals> <goal>zipalign</goal> </goals> </execution> </executions> </plugin>
Release
Bauene in release mode, so dass die Variable BuildConfig.DEBUG==false ist: ist beim plugin android-maven-plugin am besten über einen Parameter -Dandroid.release=true steuerbar.
Die Details sind hier beschrieben: http://stackoverflow.com/questions/15055961/android-maven-plugin-disable-debug-build-for-apk
clean install -Dandroid.release=true
In-App Payments
Method | Describtion | Methods | Transaction fee |
---|---|---|---|
Native Android Play In-App Payments | Billed via Google Play Account. Service https://wallet.google.com is used. | Credit Card (Visa) Debit Card (Master Card) | 30% |
Certificates
Check Signature
jarsigner -verify -verbose -certs my_application.apk
The Certificate can be extracted from an APK. Just Exctract the File META-INF\ADT.RSA from the apk file, since an apk can be entered as a normal zip. Then use the following code to convert the ADT.RSA to the certificate.
openssl.exe pkcs7 -in ADT.RSA -print_certs -inform DER -out ADT.RSA.CER
Services
Services may run, after the app was stopped.
public class MyService extends IntentService { public MyService() { super("MyService"); } @Override protected void onHandleIntent(Intent intent) { } /** * Services has to use Handlers to toast messages * @param context - the app context * @param message - the message to toast * @param handler - the handler to reuse */ public static void toastFromService(final Context context, final String message, Handler handler) { handler.post(new Runnable() { @Override public void run() { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); LOG.info("Service message: "+ message); } }); } /** Start the instance of IntentService service by sending an Intent */ public static final void start(Context context) { Intent intent = new Intent(context, MyService.class); context.startService(intent); } /** to toast stuff use the handler */ void toastMessage(final String message){ handler.post(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); } }); }
Automatic Testing
- Create a new Test Project, as [http://developer.android.com/tools/testing/testing_android.html|stated here]]
android.test.ActivityInstrumentationTestCase2<MyActivityUnderTest>
Use this clss to test activities.
- Every method in this class will be executed as a JUnitTest.
- Before every test - the method setUp() is executed
- test methods names have to start with “test”, or they won't be found
Espresso - UI tests
package digital.alf.geosound; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; import android.Manifest; import android.app.Activity; import android.os.Bundle; import androidx.navigation.NavController; import androidx.navigation.Navigation; import androidx.test.espresso.action.ViewActions; import androidx.test.espresso.matcher.ViewMatchers; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.rule.GrantPermissionRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @LargeTest public class JustOpenAllWindows { @Rule public ActivityScenarioRule<MainActivity> activityRule = new ActivityScenarioRule<>(MainActivity.class); @Rule public GrantPermissionRule mRuntimePermissionRuleFine = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION); @Rule public GrantPermissionRule mRuntimePermissionRuleCoarse = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION); @Rule public GrantPermissionRule mRuntimePermissionRuleBackground = GrantPermissionRule.grant(Manifest.permission.ACCESS_BACKGROUND_LOCATION); @Test public void openPreferences() { activityRule.getScenario().onActivity(activity -> { Bundle args = new Bundle(); NavController navController = Navigation.findNavController(activity, R.id.map); navController.navigate(R.id.action_fragment_maps_to_fragment_preferences, args); }); onView(allOf(ViewMatchers.withText(R.string.dialog_preference_label), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(matches(isDisplayed())); onView(ViewMatchers.withText(R.string.dialog_default_onout_volume_title)) .perform(ViewActions.click()); //Click on cancel button onView(ViewMatchers.withId(android.R.id.button2)) .perform(ViewActions.click()); onView(ViewMatchers.withText(R.string.dialog_default_onout_volume_title)) .perform(ViewActions.click()); //Click on cancel button onView(ViewMatchers.withId(android.R.id.button2)) .perform(ViewActions.click()); System.out.println("done"); } }
Execution in CodeBuild
Snippets
Preference Item with value in Summary
Declare an own xml attribute for custom Views. Add this to res/values/attrs.xml This attribute will contain the Mask for the summary, as known by String.format(mask, value)
<declare-styleable name="PreferenceSummaryValue"> <attr name="summaryFormat" format="string"/> </declare-styleable>
Use own attribute
<de.ivu.eticket.app.gui.PreferenceSummaryValue android:key="deadline" android:defaultValue="12" android:title="The Deadline" app:summaryFormat="end in %s days"/>
Implement own preference view
public class PreferenceSummaryValue extends EditTextPreference { private String summaryFormat = "%s"; public PreferenceSummaryValue(Context context) { super(context); init(null, context); } public PreferenceSummaryValue(Context context, AttributeSet attrs) { super(context, attrs); init(attrs, context); } public PreferenceSummaryValue(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(attrs, context); } private void init(AttributeSet attributes, Context context){ if(attributes != null){ TypedArray tarr = context.obtainStyledAttributes(attributes, R.styleable.PreferenceSummaryValue); String attribute = tarr.getString(R.styleable.PreferenceSummaryValue_summaryFormat); if(attribute != null) summaryFormat = attribute; tarr.recycle(); } } @Override public void setText(String text) { super.setText(text); setSummary(String.format(summaryFormat, text)); } @Override public void setSummary(CharSequence summary) { super.setSummary(summary); } public void setSummaryFormat(String summaryFormat) { this.summaryFormat = summaryFormat; } }
Layouts
TableLayout
- Kann wirklich nur gleichmaeßige Zellen darstellen.
- Kann kein RowSpan
- Die Rows existieren als ein besonderes Objekt “TableRow”
- Die Columns als besondere Objekte existieren nicht - es werden die CHildViews einfach gleich groß gehalten werden
- Nur gleiche layouts sollten als Columns benutzt werden. TableLayout mit 1 TabeRow und LinearLayout als Table Column klappt gut.
- Nutze Lieber GridLayout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:stretchColumns="0" > <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow> <View android:background="@color/grey_light" android:layout_height="1px"></View> <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow> </TableLayout> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow> <View android:background="@color/grey_light" android:layout_height="1px"></View> <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow> </TableLayout> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow> <View android:background="@color/grey_light" android:layout_height="1px"></View> <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow> </TableLayout> <TableLayout android:layout_width="wrap_content" android:layout_height="wrap_content" > <TableRow android:id="@+id/tableRow2" android:layout_width="wrap_content" android:layout_height="wrap_content" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="5dp" android:text="Text" android:textAppearance="?android:attr/textAppearanceMedium" /> </LinearLayout> </TableRow> </TableLayout> </TableRow> <!-- display this button in 3rd column via layout_column(zero based) --> <!-- display this button in 2nd column via layout_column(zero based) --> </TableLayout> </LinearLayout>
GridLayout
- Has row-span which tableLayout does not have
How to use GridLayout is described in this video:
LinearLayout
- Can order items in a row.
- Children must have either a given size or a weight
- You can also set the view with some content to wrap_content. Then LinearLayout will respect the size of content for this view when distributing space via weight.
- CAN NOT make a child take all the remaning space. (Use RelativeLayout for that!)
Achieving that is easy, when the width of children is given:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="50dip" android:layout_height="100dip" android:background="#cc0000"/> <TableLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <TableRow> <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#aaaa00"/> <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#00aa00"/> <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#aaaa00"/> </TableRow> <TableRow> <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#00aa00"/> <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#aaaa00"/> <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#00aa00"/> </TableRow> </TableLayout> <ImageView android:layout_width="50dip" android:layout_height="100dip" android:background="#cc0000"/> </LinearLayout>
Trying to make the table in the middle filling the remaning space fails, because the table
Example about how to use weight, width, wrap_content:
FlowLayout
There is a custom FlowLayout here https://github.com/ApmeM/android-flowlayout which allows:
- Automatical breaking the line to wrap the content, when the space is exceeded. No default layout is able to do that.
Maven dependency:
<dependency> <groupId>org.apmem.tools</groupId> <artifactId>app</artifactId> <version>1.0</version> </dependency>
Dialogs
The Dialogs are quite nice in Android. They are customizable and may have a complete custom layout.
// Create custom dialog object with layout R.layout.dialog final Dialog dialog = new Dialog(CustomDialog.this); // Include dialog.xml file dialog.setContentView(R.layout.dialog); // Set dialog title dialog.setTitle("Custom Dialog"); // set values for custom dialog components - text, image and button TextView text = (TextView) dialog.findViewById(R.id.textDialog); text.setText("Custom dialog Android example."); ImageView image = (ImageView) dialog.findViewById(R.id.imageDialog); image.setImageResource(R.drawable.image0); dialog.show(); Button declineButton = (Button) dialog.findViewById(R.id.declineButton); // if decline button is clicked, close the custom dialog declineButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Close dialog dialog.dismiss(); } });
However it is difficult to style the dialog completele! E.g. you can not make the DialogDivider in native DIalogs to have a custom color.
android-styled-dialogs
Allows to style the dialogs completele.
On GitHub | https://github.com/inmite/android-styled-dialogs |
On Maven | <dependency> <groupId>eu.inmite.android.lib</groupId> <artifactId>android-styled-dialogs</artifactId> <version>1.1.2</version> <type>apklib</type> </dependency> |
ListView AlerDialog
ListView dialogs with custom Views in the list may be very useful! They look native too so use them if possible!
To achieve that do the follwing:
1. Create a custom ListView Item
- It should Inherit from a ViewGroup
- It should have blockdescedants attribute set. Otherwise you wont be able to choose it in the list
- Achtung: you may not use the methods, which make the items clickable, focuasble … Otherwise the items in the list will not be clickable.
//cant use this for items in lists using android.content.DialogInterface.OnClickListener item.setClickable(true); item.setFocusable(true); item.setFocusableInTouchMode(true);
See this thread: http://stackoverflow.com/questions/5551042/onitemclicklistener-not-working-in-listview-android
<?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:descendantFocusability="blocksDescendants" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/rightCenteredContainer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:orientation="vertical" > <Button android:id="@+id/buttonChooseTrip" style="@style/Green.Button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="true" android:height="@dimen/btn_trip_size" android:text=">" android:width="60dp" /> </LinearLayout> <TableLayout android:layout_width="wrap_content" android:layout_height="60dp" android:layout_alignParentLeft="true" android:layout_toLeftOf="@id/rightCenteredContainer" android:gravity="center_vertical" android:padding="5dp" > <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/dialogTripsDestinations" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/transparent" android:text="München (ZOB) - Augsburg (Zentrum)" android:textStyle="bold" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/dialogTripsTimes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/transparent" android:text="16.55 - 16.10.2014" android:textSize="12dp" /> </TableRow> </TableLayout> </RelativeLayout> </LinearLayout>
2. Create a View class which will wrap the XML Layout
import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; public class DialogueTripItem extends LinearLayout{ Context mContext; TextView dialogTripsDestinations; TextView dialogTripsTimes; Button buttonChooseTrip; public DialogueTripItem(Context context) { super(context); init(context); } public DialogueTripItem(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public DialogueTripItem(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context){ this.mContext = context; LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.dialog__select_current_trip_item, this, true); dialogTripsDestinations = (TextView) findViewById(R.id.dialogTripsDestinations); dialogTripsTimes = (TextView) findViewById(R.id.dialogTripsTimes); buttonChooseTrip = (Button) findViewById(R.id.buttonChooseTrip); } public void setDestinations(String dialogTripsDestinations){ this.dialogTripsDestinations.setText(dialogTripsDestinations); } public void setDialogTripsTimes(String dialogTripsTimes){ this.dialogTripsTimes.setText(dialogTripsTimes); } public Button getButton(){ return buttonChooseTrip; } }
3. Create the AlerDialog
ListAdapter adapter = new ListAdapter(); final DialogueTripItem item = new DialogueTripItem(activity); adapter.content.add(item); AlertDialog.Builder builder = new IvuColorsAlertDialogBuilder(activity); builder.setAdapter(adapter, new android.content.DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(getApplicationContext(), "Click "+which, Toast.LENGTH_LONG).show(); } }); builder.setTitle("Title"); builder.setIcon(activity.getResources().getDrawable(R.drawable.icon_xhdpi)); builder.setNegativeButton("Cancel", cancelListener);
Installing an Application programmatically
An APK installation may be started programmatically. The use case was to put a second (library) application into the assets folder of the main applicaiton. Install the library application on demand.
Important:
- The applicaiton may not be started from the assets folder. It has to be copied to the external storage
private void installApk() { Intent intent = new Intent(Intent.ACTION_VIEW); // the name of the apk in the assests folder String assetFileName = "CommonBarcode115.apk"; // location where the app will be temporary copied to File cacheFolder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"//Android//data//de.example.maintestapp"); // the ful path to the cache file File fileCache = new File(cacheFolder, assetFileName); // copy the file to the cache try { // needs android.permission.WRITE_EXTERNAL_STORAGE fileCache.getParentFile().mkdirs(); fileCache.createNewFile(); // delete old file if (fileCache.exists()) { fileCache.delete(); } copyAsset(assetFileName, cacheFolder); } catch (Exception e) { Toast.makeText(getApplicationContext(), "Ein Fehler beim kopieren", Toast.LENGTH_LONG); } if (!fileCache.exists()) { // if (!assetExists(this.getAssets(), assetFile)) { Toast.makeText(getApplicationContext(), "APK not found", Toast.LENGTH_LONG).show(); return; } intent.setDataAndType(Uri.fromFile(fileCache),"application/vnd.android.package-archive"); startActivity(intent); } private void copyAsset(String assetFileName, File copyToFolderPath) { AssetManager assetManager = getAssets(); String[] files = null; try { files = assetManager.list(""); } catch (IOException e) { Toast.makeText(getApplicationContext(), "Failed to load assets", Toast.LENGTH_LONG).show(); } for(String filename : files) { if(!filename.equals(assetFileName)){ continue; } InputStream in = null; OutputStream out = null; try { in = assetManager.open(filename); File outFile = new File(copyToFolderPath, filename); out = new FileOutputStream(outFile); copyFile(in, out); in.close(); in = null; out.flush(); out.close(); out = null; } catch(IOException e) { Toast.makeText(getApplicationContext(), "Failed to copy asset file", Toast.LENGTH_LONG).show(); } } } private void copyFile(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } } private static boolean assetExists(AssetManager assets, String name) { try { // using File to extract path / filename // alternatively use name.lastIndexOf("/") to extract the path File f = new File(name); String parent = f.getParent(); if (parent == null) parent = ""; String fileName = f.getName(); // now use path to list all files String[] assetList = assets.list(parent); if (assetList != null && assetList.length > 0) { for (String item : assetList) { if (fileName.equals(item)) return true; } } } catch (IOException e) { // Log.w(TAG, e); // enable to log errors } return false; }
Debugging HTTP Communication
To Capture HTTP Communication of Android, to simulate slow connection etc. use the following Tools:
- Genymotion Simulator - http://www.genymotion.com/
- Fiddler - http://www.telerik.com/fiddler
- Connectify as alternative to Genymotion - http://www.connectify.me/
In your virtual device,
- Go to Android settings menu
- In Wireless & Networks section, select Wi-Fi
- Press and hold for 2 seconds WiredSSID network in the list
- Choose Modify Network
- Check Show advanced options
- Select Manual for Proxy settings menu entry
- Enter the proxy address: the Fiddler-running PC's IPAddress and Port 8888
- Press the Save button
In Fiddler,
- Click Tools menu > Fiddler Options > Connections
- Tick the Allow Remote Computers to connect box
- Restart Fiddler.
As an alternative to Genymotion
Use the Connectify, to connect your Phone with the Net via the PC with Fiddler.
Configure FIddler as described above.
Fragments
Fallpits
- Fragments can not be nested. So use Fragments only for top level pieces when planning app's layout
Fragments can be defined in XML by using a
- a fragment tag
- a class attribute
<fragment android:id="@+id/fragmentMainContent" class="com.amberfog.mapslidingtest.app.FragmentMainContent" android:layout_width="match_parent" android:layout_height="match_parent"/>
The minimal fragment class looks as following
public class FragmentMainContent extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_maincontent_layout, container,false); } }
where the fragment_maincontent_layout may look as whatever you like. For example:
<?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" > <GridLayout android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="1" > <AnalogClock android:id="@+id/analogClock1" android:layout_column="0" android:layout_gravity="center" android:layout_row="0" /> </GridLayout> </LinearLayout>
- The function, which makes Android restore the Fragment without information loss fragment.setRetainInstance(true) makes the platform find the fragment on configuration change and re-instantiate it, when the application lifecycle is restarted on configuraiton change.
This means that the Application has to make sure, that the newly restored fragment is not overridden onCreate()// ACHTUNG: when using setRetainInstance(true) on a fragment to restore it's vars on configchange - check whether the fragment already exists before recreating it this.fragmentDetails = (Fragment1Details) fragmentManager.findFragmentByTag("fragmentTag"); if(this.fragmentDetails==null) { // create the fragment here and tag it with "fragmentTag" }
- The function fragment.setRetainInstance(true) only works, if the fragment is not added to the back stack
Class | Usage | Details |
---|---|---|
FragmentManager | Activity.getFragmentManager() | Used to control the Fragments |
FragmentTransaction | getFragmentManager().beginTransaction() | Is able to create, update, remove fragments |
Fragments may be either created in xml or programmatically.
In XML
<fragment android:id="@+id/fragmentId" android:layout_width="wrap_content" android:layout_height="wrap_content"> </fragment>
Programmatically. Programmatically fragment operations are done via FragmentManager which create FragmentTranscation
ACHTUNG: The fragment must live inside a ViewGroup R.id.fragmentContainer ist such a ViewGroup here
// get Fragment manager FragmentManager fragmentManager = getFragmentManager(); // ACHTUNG: when using setRetainInstance(true) on a fragment to restore it's vars on configchange - check whether the fragment already exists before recreating it this.fragmentDetails = (Fragment1Details) fragmentManager.findFragmentByTag("fragmentDetails"); if(this.fragmentDetails==null) { // start a transaction FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // create a fragment this.fragmentDetails = new Fragment1Details(); // set the id to the fragment // The Fragment should not be recreated when the Application is killed this.fragmentDetails.setRetainInstance(true); // add the fragment to the view fragmentTransaction.add(R.id.fragmentContainer, this.fragmentDetails, "fragmentDetails"); // //make transaction reversable by undoing the action on the fragmentmanager // fragmentTransaction.addToBackStack(null); // commit the transaction fragmentTransaction.commit(); // init the value from model of fragmentDetails. myActivityModel.setText(myActivityModel.getText()); }else{ Log.d(TAG,"Fragment already exists. Do not recreate it!"); }
ScrolView
To scroll the View continous prgrammatic scrolling.
To scroll continously the view has to be scrolled by a little delta in a regular time period.
The period of time should be nearly 24 Frames per second, which is every 41 ms.
The distance-change in px per ms depend from the speed you wish to achieve.
0.1 - 0.2 PX per MS | Slow movement |
0.4 - 0.7 PX per MS | Middle speed |
1 PX - 1.4 PX per MS | Fast speed |
12PFS | Seems to be not enough eince you can see the picture jerking | ![]() |
24PFS | Seems to be ok | ![]() |
36PFS | No difference to 24FPS | ![]() |
COde to test differend scroll speeds
return new Thread(new Runnable() { @Override public void run() { // scroll time int scrollTimeMs = 4000; // SPEED IN PX / MS, IF THE IMAGE WULD MOVE CONTIONOUSLY double pxPerMsStart = 0.2; double pxPerEnd = 0.8; double pxPerMsChangeBy = 0.1; // FPS int fpsStart = 24; //per sec int fpsEnd = 24; int fpsChangeBy = 12; // iterate pause time for(double scrollspeedPxPerMs = pxPerMsStart; scrollspeedPxPerMs <=pxPerEnd; scrollspeedPxPerMs +=pxPerMsChangeBy){ // iterate steps for(int fps=fpsStart; fps<= fpsEnd; fps+=fpsChangeBy){ // pauses in ms are computed from fps pauseMs = 1000/fps; /* the speed is given for the case, when the image moves contingously because there is a redraw pause between movements - we have to multiply the pause in MS with the speed to know to which amount the image has to be moved after every pause */ steppx = (int) Math.round((double)pauseMs * scrollspeedPxPerMs); // timestemp for next iteration timestampWhenToStop = System.currentTimeMillis() + scrollTimeMs; // annouce settings final String message = String.format("Step by %s px, every %s ms - FPS: %s Scroll by %s px per MS", steppx, pauseMs, fps, scrollspeedPxPerMs ); Log.d("stepscroll", message); // jump to the top ViewGroupAnimatedActivity6.this.runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); scrollView.setSmoothScrollingEnabled(false); scrollView.scrollTo(0, 0); scrollView.setSmoothScrollingEnabled(true); } }); // scroll to the bottom boolean stopIteration = false; while (!stopIteration){ ViewGroupAnimatedActivity6.this.runOnUiThread(new Runnable() { public void run() { scrollView.smoothScrollBy(0, steppx); } }); if(timestampWhenToStop < System.currentTimeMillis()){ stopIteration = true; continue; } try { Thread.sleep(pauseMs); } catch (InterruptedException e) { e.printStackTrace(); } if(!isRunning){ return; } } }// for fps } // for pxPerEnd isRunning = false; } }); }
Gradle
To execute gradle tasks - a gradle wrapper file is used. The wrapper is a shell / batch script, which downloads the gradle and so does not require gradle to be installed previously. The wrapper uses it's own gradle version The name of the file is gradlew.bat
To generate the gradle wrapper use gradle and add the wrapper :
gradle wrapper
AIDL
aidl
- all aidl have to be located in th same packages - can pass Parcels or primitives - implement aidl to be able to retrieve a common interface to communicate between different applicaitons, which are different processes
public void onServiceConnected(ComponentName name, IBinder service) { IServiceReadTicketBinder serviceReadTicketBinder = IServiceReadTicketBinder.Stub.asInterface(service); // IServiceReadTicketBinder serviceReadTicketBinder = (IServiceReadTicketBinder)service; // not allowed when retriveing service from another application. service var is of type proxy
- add in out inout as stated here http://www.app-solut.com/blog/2011/05/using-self-defined-parcelable-objects-during-an-android-aidl-rpc-ipc-call/ - use Bundle.class to exchange data with the Service. It provides possibility to restore the stored values typesafe. Bundle is like a Hashmap which can store different types of values result.getString(“Test”)
Bindings
The bare minimum in code
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final ViewDataBinding binding = DataBindingUtil.inflate( inflater, R.layout.fragment_poidialog, container, false); final View view = binding.getRoot(); return view; }
The binding in xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/layout_common" app:passedText="@{@string/app_name}" // here we pass any String /> </LinearLayout> </layout> <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" > <data> // declare fields <variable name="passedText" type="String"/> </data> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{passedText}"/> //set field to your view. </layout>
Style
Styles, attributes
TextAppearance
The text size in android is controlled by “TextAppearance”
Material Design provides 13 type “styles” that are applied to all the text in your app. Each of these have a design term (eg. “Body 1”) along with a corresponding type attribute that can be overridden in your app theme (eg. textAppearanceBody1). There are default “baseline” values (text size, letter spacing, capitalization, etc.) for each style.
Usage in your view
At the end, to express which size the text in your custom view should have instead of influencing “textSize” directly which will lead to difficulties with consistancy across app and consistancy with android native textSizes
you should set “android:textAppearance=” on your view.
and the value should be one of androids own textAppearance attributes:
- textAppearanceHeadline1
- textAppearanceHeadline2
- textAppearanceHeadline3
- textAppearanceHeadline4
- textAppearanceBody1
- textAppearanceBody2
The same attributes are used by android too, so your view will be consistant with android natives
In your someAppPartLayout.xml
<!-- My title in my app view --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Title" android:textAppearance="?attr/textAppearanceHeadline4" />
Changing the value of attributes in AppTheme
In your theme, maybe defined in your styles.xml you can change single android attributes.
In my theme, which is defined in this style - overriding the single attributes “textAppearance*” referencing MY styles, which can interfere and override the default values for textAppearance.
Remark: Those textAppearance* attributes are also used in android native views, like buttons see https://material.io/blog/android-material-theme-type. So overriding those in a theme - changes the behavior also for Android native elements
THose attributes are not just strings, but are introduced as typed attributes in attrs.xml with format=“reference”. format=“reference” means, that one can take a “<style></style>” element as a value. In the value-style element - the items a la android:textSize are expected (convention).
USAGE of attributes The attribute can be USED in views, by refering to the attribute in the view e.g.
android:textAppearance="?attr/textAppearanceHeadline4" />
In your styles.xml pointing textAppearanceHeadline1 attribute of type “reference” to my own style.
The style inherits from androids own style but I still can override some values in my inherited style.
<!-- Base application theme. --> <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="textAppearanceHeadline1"> @style/TextAppearance.Headline1 </item>
Introducing own styles, to refer from overridden attributes textAppearance*
THe value of an overridden attribute textAppearance* is a style
in your styles.xml parallel to the AppTheme - introduce styles, to refer from attributes.
<!-- here I can change attributes of the STYLES. by Inheriting from "TextAppearance.MaterialComponents.*" I inherit all the native properties but get the possibility to override them here --> <style name="TextAppearance.Headline1" parent="TextAppearance.MaterialComponents.Headline1"> <item name="android:textSize">96sp</item> </style> <style name="TextAppearance.Headline2" parent="TextAppearance.MaterialComponents.Headline2"> <item name="android:textSize">60sp</item> </style> <style name="TextAppearance.Headline3" parent="TextAppearance.MaterialComponents.Headline3"> <item name="android:textSize">48sp</item> </style> <style name="TextAppearance.Headline4" parent="TextAppearance.MaterialComponents.Headline4"> <item name="android:textSize">34sp</item> </style> <style name="TextAppearance.Headline5" parent="TextAppearance.MaterialComponents.Headline5"> <item name="android:textSize">24sp</item> </style> <style name="TextAppearance.Headline6" parent="TextAppearance.MaterialComponents.Headline6"> <item name="android:textSize">20sp</item> </style> <style name="TextAppearance.Body1" parent="TextAppearance.MaterialComponents.Body1"> <item name="android:textSize">16sp</item> </style> <style name="TextAppearance.Body2" parent="TextAppearance.MaterialComponents.Body2"> <item name="android:textSize">14sp</item> </style>