03 - Android Internals

  • Description: WakeLocks, Low-memory Killer, Ashmem, Alarm, Logger, Binder, System Server, Dalvik, ART, Zygote, Service Manager, Activity Manager, Package Manager, Power Manager.
  • Practical part:

Lecture

Practical

Resources

Files

Task 1 - Launching an Activity (3p)

Create a new project and add a button to the main activity layout. Get a reference to this button in the onCreate method in MainActivity. Add a new Activity to the project. You can automatically add a new activity by right clicking on the project and selecting New > Activity > Blank Activity from the menu. This will start a wizard similar to the one at application creation.

Back in MainActivity, add code to the onClick of the OnClickListener so that the new activity gets started. To start a new Activity you have to create an intent first which will explicitly take two parameters: a Context and the Class of the second activity. A context represents an interface to the current state of the application. It allows access to application specific resources and operations such as launching an activity or starting a service. In order to obtain the context you can use a reference to the current instance of the MainActivity class.

public void onClick(View v) {
        // Replace NewActivity with the name of your secondary activity
	Intent i = new Intent(MainActivity.this, NewActivity.class);
}

In order to launch the activity you must call:

startActivity(i);

Before launching the new activity, it must be declared in the AndroidManifest.xml file. If you manually created the activity , you will have to add a similar line to the following one to the manifest file. If you created the activity through the wizard, the line should have been added automatically.

<activity
   android:name="com.example.test.NewActivity" />

If you created the Activity manually, in the onCreate of the second activity make sure you call the onCreate of the super class and use setContentView to the layout of the second activity for which you will have to create an empty layout.

super.onCreate(savedInstanceState);
setContentView(R.layout.new_activity);

Intents can also contain a dictionary of data (called Intent extras). Add an EditText box to your main activity layout and a TextView to your second activity ​layout. When clicking the button, get the text from the EditText element and add it to the intent.

i.putExtra("key", "value");

Then, in your second activity, first, you have to access the intent through which the activity was launched. This can be done using the getIntent method. Afterwards, get the string from the intent and display it in the TextView. (Hint: Intent.getStringExtra ​and Activity.getIntent)

Task 2 - Lists and Adapters (4p)

For this task, you will have to unpack the lab archive and copy the three files into the current project. Copy MyMenuItem.java and MenuAdapter.java in the same directory where the activity files reside. Copy list_item.xml in res/layout.

A common widget for an Android application are lists which contain multiple items. Each item in the list has a layout and, usually, all items in a list have the same layout. To account for this, ListViews must have an adapter which creates the View for each item. When you want to display custom items within a list, the easiest way is to extend the BaseAdapter class.

In this task, we will implement a list for which each list item (MyMenuItem) will have list_item.xml as its corresponding layout. If you take a closer look at this layout, you will notice that it contains 3 TextView elements.

Add a ListView to your secondary activity and get a reference to it (ListView can be found under the Containers tab in the Design tab of the layout file). As previously mentioned, the adapter is responsible for creating and displaying the view for each list item through the getView method of the MenuAdapter class.

In order to create a view, we will need to “inflate” the xml layout. For this we have to get the standard LayoutInflater object which is already hooked in the context:

LayoutInflater inflater = (LayoutInflater)context.getSystemService
      (Context.LAYOUT_INFLATER_SERVICE);

To inflate a view use the inflater you obtained and call the inflate method, giving it the list_item layout and a null ViewGroup. Afterwards, we need a reference to the list item corresponding to the position argument from the getView method (Hint: getItem). Then, set the correct text on the TextViews inside the new View. Use findViewById on the View to get the TextViews.

You can use this code to test your getView implementation:

ListView lv = (ListView) findViewById(R.id.listView1);

List<MyMenuItem> items = new ArrayList<MyMenuItem>();
items.add(new MyMenuItem("Coffee", 6, "Simple black coffee"));
items.add(new MyMenuItem("Caffe latte", 7, "Coffee with milk"));
items.add(new MyMenuItem("Espresso", 7, "Simple espresso"));
items.add(new MyMenuItem("Caffe macchiatto", 8, "Espresso with foamed milk"));

MenuAdapter adapter = new MenuAdapter(this, items);
lv.setAdapter(adapter);

Task 3 - Data Storage (3p)

We want to provide a method for persistently storing menu items within the application, while also allowing the user to add new ones. For this, modify the behavior of the first EditText (from the first activity) such that its content will not be sent through the intent to the second activity. We will use it for adding the name of a new item. Add other two EditText elements, one for the description and the other one for the price of new items. Also add a new Button, with the “Add” label. When the user clicks it, we want to take the contents of the three EditText elements and add a new item to a database. The second activity will query this database in order to list available items.

The first step in creating a database is to have a class that extends the SQLiteOpenHelper android class, which will be responsible for creating the database, while also allowing read and write operations. However, these operations will not be handled by it directly, but by our own ContentProvider class. As such, the SQLiteOpenHelper class should only contain this:

public class DatabaseHelper extends SQLiteOpenHelper{
    private static final String DB_NAME = "menu.db";
    private static final int DB_VERSION = 1;

    public DatabaseHelper(Context context) {
        super(context,DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE IF NOT EXISTS menu (" +
                "id INTEGER PRIMARY KEY," +
                "name TEXT," +
                "desc TEXT," +
                "price INTEGER" +
                ");");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS menu");
        onCreate(db);
    }
}

Now, create a new ContentProvider class using the creation wizard. You will be prompted for an authority, which is typically chosen as the class name and the package name(for e.g. “ndk.lab2.ItemsProvider”). Afterwards, we need to add some constants that will be involved in Uri matching:

public class ItemsProvider extends ContentProvider {
    
    public static final int TABLE = 1;
    public static final int ROW = 2;

    // Change the uriRoot to the proper package name
    public static final String uriRoot = "ndk.lab2.ItemsProvider";
    public static final String tableUri = "content://" + uriRoot + "/menu";

    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static{
        uriMatcher.addURI(uriRoot, "menu", TABLE);
        uriMatcher.addURI(uriRoot, "menu/#", TABLE);
    }
...
}

Check the manifest file after you created the ContentProvider class. You will notice that the content provider component was automatically added there.

We will also need a database helper instance within our provider, which should be initialized within the provider's onCreate() method:

public class ItemsProvider extends ContentProvider {
...
    private DatabaseHelper dbHelper;

    @Override
    public boolean onCreate() {
        dbHelper = new DatabaseHelper(getContext());

        return true;
    }
...
}

Next we will implement the insert and query methods for the content provider. For insert, we will check if the URI matches the table, insert the ContentValue in the table and return the added row's URI. For query, we should create and return a cursor object which will allow random read-write access to a database query. We will use the DatabaseHelper to access the database (see getReadableDatabase, getWritableDatabase), SQLiteQueryBuilder to create a query and the UriMatcher to check URI type and validity.

For insert, we will use this code:

 @Override
    public Uri insert(Uri uri, ContentValues values) {
        // handle requests to insert a new row.

        if (uriMatcher.match(uri) != TABLE) {
            throw new UnsupportedOperationException("Should insert to menu table");
        }
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        long rowId = db.insert("menu", null, values);
        if (rowId >= 0) {
            // Appends the URI to the tableUri path
            Uri noteUri = ContentUris.withAppendedId(Uri.parse(tableUri), rowId);
            getContext().getContentResolver().notifyChange(noteUri, null);
            return noteUri;
        } else {
            throw new UnsupportedOperationException("Failed to insert row into " + uri);
        }
    }

For query, the following code will be used:

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // handle query requests from clients.

        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setTables("menu");

        switch (uriMatcher.match(uri)) {
            case TABLE:
                break;
            case ROW:
                queryBuilder.appendWhereEscapeString("id = " + uri.getLastPathSegment());
                break;
            default:
                throw  new IllegalArgumentException("Wrong URI: " + uri);
        }

        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);

        return cursor;
    }

Then, to test, insert items into the database in the MainActivity, and query the database and build the list from the database in the second activity.

Example inserting an item into the database:

addButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ContentValues cv = new ContentValues();
        cv.put("name", name.getText().toString());
        cv.put("desc", desc.getText().toString());
        cv.put("price", Integer.parseInt(price.getText().toString()));

        Uri uri = getContentResolver().insert(Uri.parse(ItemsProvider.tableUri), cv);
        Log.d("[ndk][lab2]","Result of adding new item: " + uri.toString());
    }
});
osp/lectures/lecture-internals.txt · Last modified: 2016/11/06 18:47 by laura.gheorghe
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0