12.4 DrySister View Girls App (Version 1) – 4. Adding Data Caching (Using SQLite)
Category Android Basic Tutorial
1. Some Intro
In the previous section, we created an asynchronous image loading and caching framework for DrySister – SisterLoader (Girl Loader). It successfully cached images from the network on disk and in memory. When we disconnect from the network, we can still view these images. However, you might have noticed an awkward issue: when we enter the app with an internet connection, we get image-related information such as URLs. If we exit the app, disconnect from the internet, and then re-enter, the images won't load because we don't have the URLs for the cached images, making it somewhat useless. Therefore, we need to store the data returned by the backend interface. Each time we load data, we store the returned information locally, and when there's no network, we can load the local data! So, in this section, we will add SQLite to DrySister to store related data. The tasks are clear, let's get started~
2. Start Coding
Create a new db branch on develop to write the code for this section.
First, add a utility class to check the network status: NetworkUtils.java, with a simple method to check if the network is available. We also need to add a permission in AndroidManifest.xml: android.permission.ACCESS.
public class NetworkUtils {
/** Get network information */
private static NetworkInfo getActiveNetworkInfo(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo();
}
/** Check if network is available */
public static boolean isAvailable(Context context) {
NetworkInfo info = getActiveNetworkInfo(context);
return info != null && info.isAvailable();
}
}
Next, define a class for database field constants: TableDefine.java, where we write the database name and fields:
public class TableDefine {
public static final String TABLE_FULI = "fuli";
public static final String COLUMN_ID = "id";
public static final String COLUMN_FULI_ID = "_id";
public static final String COLUMN_FULI_CREATEAT = "createAt";
public static final String COLUMN_FULI_DESC = "desc";
public static final String COLUMN_FULI_PUBLISHEDAT = "publishedAt";
public static final String COLUMN_FULI_SOURCE = "source";
public static final String COLUMN_FULI_TYPE = "type";
public static final String COLUMN_FULI_URL = "url";
public static final String COLUMN_FULI_USED = "used";
public static final String COLUMN_FULI_WHO = "who";
}
Then, write the database creation class: SisterOpenHelper.java, to create the database:
public class SisterOpenHelper extends SQLiteOpenHelper{
private static final String DB_NAME = "sister.db"; // Database name
private static final int DB_VERSION = 1; // Database version number
public SisterOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String createTableSql = "CREATE TABLE IF NOT EXISTS " + TableDefine.TABLE_FULI + " ("
+ TableDefine.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ TableDefine.COLUMN_FULI_ID + " TEXT, "
+ TableDefine.COLUMN_FULI_CREATEAT + " TEXT, "
+ TableDefine.COLUMN_FULI_DESC + " TEXT, "
+ TableDefine.COLUMN_FULI_PUBLISHEDAT + " TEXT, "
+ TableDefine.COLUMN_FULI_SOURCE + " TEXT, "
+ TableDefine.COLUMN_FULI_TYPE + " TEXT, "
+ TableDefine.COLUMN_FULI_URL + " TEXT, "
+ TableDefine.COLUMN_FULI_USED + " BOOLEAN, "
+ TableDefine.COLUMN_FULI_WHO + " TEXT"
+ ")";
db.execSQL(createTableSql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
}
Next, we need to write a database operation class for adding, deleting, updating, querying, and paginating operations. We'll make this class a singleton: SisterDBHelper.java, with methods like these:
public class SisterDBHelper {
private static final String TAG = "SisterDBHelper";
private static SisterDBHelper dbHelper;
private SisterOpenHelper sqlHelper;
private SQLiteDatabase db;
private SisterDBHelper() {
sqlHelper = new SisterOpenHelper(DrySisterApp.getContext());
}
/** Singleton */
public static SisterDBHelper getInstance() {
if(dbHelper == null) {
synchronized (SisterDBHelper.class) {
if(dbHelper == null) {
dbHelper = new SisterDBHelper();
}
}
}
return dbHelper;
}
/** Insert a girl */
public void insertSister(Sister sister) {
db = getWritableDB();
ContentValues contentValues = new ContentValues();
contentValues.put(TableDefine.COLUMN_FULI_ID,sister.get_id());
contentValues.put(TableDefine.COLUMN_FULI_CREATEAT,sister.getCreateAt());
contentValues.put(TableDefine.COLUMN_FULI_DESC,sister.getDesc());
contentValues.put(TableDefine.COLUMN_FULI_PUBLISHEDAT,sister.getPublishedAt());
contentValues.put(TableDefine.COLUMN_FULI_SOURCE,sister.getSource());
contentValues.put(TableDefine.COLUMN_FULI_TYPE,sister.getType());
contentValues.put(TableDefine.COLUMN_FULI_URL,sister.getUrl());
contentValues.put(TableDefine.COLUMN_FULI_USED,sister.getUsed());
contentValues.put(TableDefine.COLUMN_FULI_WHO,sister.getWho());
db.insert(TableDefine.TABLE_FULI,null,contentValues);
closeIO(null);
}
/** Insert a bunch of girls (using transaction) */
public void insertSisters(ArrayList<Sister> sisters) {
db = getWritableDB();
db.beginTransaction();
try{
for (Sister sister: sisters) {
ContentValues contentValues = new ContentValues();
contentValues.put(TableDefine.COLUMN_FULI_ID,sister.get_id());
contentValues.put(TableDefine.COLUMN_FULI_CREATEAT, sister.getCreateAt());
contentValues.put(TableDefine.COLUMN_FULI_DESC, sister.getDesc());
contentValues.put(TableDefine.COLUMN_FULI_PUBLISHEDAT, sister.getPublishedAt());
contentValues.put(TableDefine.COLUMN_FULI_SOURCE, sister.getSource());
contentValues.put(TableDefine.COLUMN_FULI_TYPE, sister.getType());
contentValues.put(TableDefine.COLUMN_FULI_URL, sister.getUrl());
contentValues.put(TableDefine.COLUMN_FULI_USED, sister.getUsed());
contentValues.put(TableDefine.COLUMN_FULI_WHO, sister.getWho());
db.insert(TableDefine.TABLE_FULI, null, contentValues);
}
db.setTransactionSuccessful();
} finally {
if (db != null && db.isOpen()) {
db.endTransaction();
closeIO(null);
}
}
}
/** Delete a sister (by _id) */
public void deleteSister(String _id) {
db = getWritableDB();
db.delete(TableDefine.TABLE_FULI, "_id =?", new String[]{_id});
closeIO(null);
}
/** Delete all sisters */
public void deleteAllSisters() {
db = getWritableDB();
db.delete(TableDefine.TABLE_FULI, null, null);
closeIO(null);
}
/** Update sister information (by _id) */
public void updateSister(String _id, Sister sister) {
db = getWritableDB();
ContentValues contentValues = new ContentValues();
contentValues.put(TableDefine.COLUMN_FULI_ID, sister.get_id());
contentValues.put(TableDefine.COLUMN_FULI_CREATEAT, sister.getCreateAt());
contentValues.put(TableDefine.COLUMN_FULI_DESC, sister.getDesc());
contentValues.put(TableDefine.COLUMN_FULI_PUBLISHEDAT, sister.getPublishedAt());
contentValues.put(TableDefine.COLUMN_FULI_SOURCE, sister.getSource());
contentValues.put(TableDefine.COLUMN_FULI_TYPE, sister.getType());
contentValues.put(TableDefine.COLUMN_FULI_URL, sister.getUrl());
contentValues.put(TableDefine.COLUMN_FULI_USED, sister.getUsed());
contentValues.put(TableDefine.COLUMN_FULI_WHO, sister.getWho());
db.update(TableDefine.TABLE_FULI, contentValues, "_id =?", new String[]{_id});
closeIO(null);
}
/** Query the number of sisters in the current table */
public int getSistersCount() {
db = getReadableDB();
Cursor cursor = db.rawQuery("SELECT COUNT (*) FROM " + TableDefine.TABLE_FULI, null);
cursor.moveToFirst();
int count = cursor.getInt(0);
Log.v(TAG, "count: " + count);
closeIO(cursor);
return count;
}
/** Paginated query of sisters, parameters are the current page and the number per page, page number starts from 0 */
public List<Sister> getSistersLimit(int curPage, int limit) {
db = getReadableDB();
List<Sister> sisters = new ArrayList<>();
String startPos = String.valueOf(curPage * limit); // Data start position
if (db != null) {
Cursor cursor = db.query(TableDefine.TABLE_FULI, new String[]{
TableDefine.COLUMN_FULI_ID, TableDefine.COLUMN_FULI_CREATEAT,
TableDefine.COLUMN_FULI_DESC, TableDefine.COLUMN_FULI_PUBLISHEDAT,
TableDefine.COLUMN_FULI_SOURCE, TableDefine.COLUMN_FULI_TYPE,
TableDefine.COLUMN_FULI_URL, TableDefine.COLUMN_FULI_USED,
TableDefine.COLUMN_FULI_WHO,
}, null, null, null, null, TableDefine.COLUMN_ID, startPos + "," + limit);
while (cursor.moveToNext()) {
Sister sister = new Sister();
sister.set_id(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_ID)));
sister.setCreateAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_CREATEAT)));
sister.setDesc(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_DESC)));
sister.setPublishedAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_PUBLISHEDAT)));
sister.setSource(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_SOURCE)));
sister.setType(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_TYPE)));
sister.setUrl(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_URL)));
sister.setUsed(cursor.getInt(cursor.getColumnIndex(TableDefine.COLUMN_FULI_USED)));
sisters.add(sister);
}
closeIO(cursor);
}
return sisters;
}
/** Query all sisters */
public List<Sister> getAllSisters() {
db = getReadableDB();
List<Sister> sisters = new ArrayList<>();
Cursor cursor = db.rawQuery("SELECT * FROM " + TableDefine.TABLE_FULI, null);
cursor.moveToFirst();
while (cursor.moveToNext()) {
Sister sister = new Sister();
sister.set_id(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_ID)));
sister.setCreateAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_CREATEAT)));
sister.setDesc(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_DESC)));
sister.setPublishedAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_PUBLISHEDAT)));
sister.setSource(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_SOURCE)));
sister.setType(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_TYPE)));
sister.setUrl(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_URL)));
sister.setUsed(cursor.getInt(cursor.getColumnIndex(TableDefine.COLUMN_FULI_USED)));
sisters.add(sister);
}
closeIO(cursor);
return sisters;
}
/** Method to get writable database */
private SQLiteDatabase getWritableDB() {
return sqlHelper.getWritableDatabase();
}
/** Method to get readable database */
private SQLiteDatabase getReadableDB() {
return sqlHelper.getReadableDatabase();
}
/** Method to close cursor and database */
private void closeIO(Cursor cursor) {
if(cursor != null) {
cursor.close();
}
if(db != null) {
db.close();
}
}
}
It's quite straightforward, just some SQLite method calls. Next, let's modify the layout by changing activity_main.xml to the following code, which includes previous and next buttons:
<?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/btn_previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="invisible"
android:text="Previous"/>
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/btn_previous"
android:text="Next"/>
<ImageView
android:id="@+id/img_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/btn_previous"/>
</RelativeLayout>
Finally, some logic changes are needed in MainActivity.java, focusing on:
- Control and handling of curPos and page
- Loading data from the network or database based on network availability
- Avoiding duplicate insertion when storing network data into the database, etc.
Implementation:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button previousBtn;
private Button nextBtn;
private ImageView showImg;
private ArrayList<Sister> data;
private int curPos = 0; // Current position of the displayed image
private int page = 1; // Current page number
private PictureLoader loader;
private SisterApi sisterApi;
private SisterTask sisterTask;
private SisterLoader mLoader;
private SisterDBHelper mDbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sisterApi = new SisterApi();
loader = new PictureLoader();
mLoader = SisterLoader.getInstance(MainActivity.this);
mDbHelper = SisterDBHelper.getInstance();
initData();
initUI();
}
private void initData() {
data = new ArrayList<>();
sisterTask = new SisterTask();
sisterTask.execute();
}
private void initUI() {
previousBtn = (Button) findViewById(R.id.btn_previous);
nextBtn = (Button) findViewById(R.id.btn_next);
showImg = (ImageView) findViewById(R.id.img_show);
previousBtn.setOnClickListener(this);
nextBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_previous:
--curPos;
if (curPos == 0) {
previousBtn.setVisibility(View.INVISIBLE);
}
if (curPos == data.size() - 1) {
sisterTask = new SisterTask();
sisterTask.execute();
} else if(curPos < data.size()) {
mLoader.bindBitmap(data.get(curPos).getUrl(), showImg, 400, 400);
}
break;
case R.id.btn_next:
previousBtn.setVisibility(View.VISIBLE);
if(curPos < data.size()) {
++curPos;
}
if (curPos > data.size() - 1) {
sisterTask = new SisterTask();
sisterTask.execute();
} else if(curPos < data.size()){
mLoader.bindBitmap(data.get(curPos).getUrl(), showImg, 400, 400);
}
break;
}
}
private class SisterTask extends AsyncTask<Void, Void, ArrayList<Sister>> {
public SisterTask() {
}
@Override
protected ArrayList<Sister> doInBackground(Void... params) {
ArrayList<Sister> result = new ArrayList<>();
if (page < (curPos + 1) / 10 + 1) {
++page;
}
// Check if network is available
if (NetworkUtils.isAvailable(getApplicationContext())) {
result = sisterApi.fetchSister(10, page);
// Query the database for the number of sisters to avoid duplicate insertion
if (mDbHelper.getSistersCount() / 10 < page) {
mDbHelper.insertSisters(result);
} else {
result.clear();
result.addAll(mDbHelper.getSistersLimit(page - 1, 10));
}
return result;
}
@Override
protected void onPostExecute(ArrayList<Sister> sisters) {
super.onPostExecute(sisters);
data.addAll(sisters);
if (data.size() > 0 && curPos + 1 < data.size()) {
mLoader.bindBitmap(data.get(curPos).getUrl(), showImg, 400, 400);
}
}
@Override
protected void onCancelled() {
super.onCancelled();
sisterTask = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (sisterTask != null) {
sisterTask.cancel(true);
}
}
}
3. Running Effect Diagram
Operation Steps:
After starting the project, keep pressing the "Next Page" button to cache a bunch of images. Then disconnect the network and enter DrySister. Duang~, magically, sisters appear, and you can switch pictures by pressing "Next Page" or "Previous Page". Mom no longer has to worry about me not being able to view DrySister when there's no internet!
Running Screenshots:
4. Summary
Alright, this section added SQLite to save backend data on top of the previous section, allowing you to view sister pictures even without an internet connection. The major code changes are as described above, with some minor tweaks, see the code for details. Finally, merge the db branch into the develop branch, then delete the db branch, and push the updated develop branch to Github!
Code Download:
https://github.com/coder-pig/DrySister/tree/develop Feel free to follow, star, and if you have any suggestions, please raise an issue!
-1.0 Android Basic Introduction Tutorial
-1.0.1 2015 Latest Android Basic Introduction Tutorial Table of Contents
-1.1 Background and System Architecture Analysis
-1.2 Development Environment Setup
-1.2.1 Developing Android APP with Eclipse + ADT + SDK
-1.2.2 Developing Android APP with Android Studio
-1.3 Solving SDK Update Issues
-1.4 Genymotion Emulator Installation
-1.5.1 Git Tutorial for Basic Local Repository Operations
-1.5.2 Git: Setting Up a Remote Repository on GitHub
-1.6 How to Play with 9-Patch Images
-1.7 Interface Prototype Design
-1.8 Project Source Analysis (Various Files, Resource Access)
-1.9 Android Program Signing and Packaging
-1.11 Decompiling APK to Retrieve Code & Resources
-2.1 Concepts of View and ViewGroup
-2.2.1 LinearLayout (Linear Layout)
-2.2.2 RelativeLayout (Relative Layout)
-2.2.3 TableLayout (Table Layout)
-2.2.4 FrameLayout (Frame Layout)
-2.2.5 GridLayout (Grid Layout)
-2.2.6 AbsoluteLayout (Absolute Layout)
-2.3.1 TextView (Text View) Detailed Explanation
-2.3.2 EditText (Input Box) Detailed Explanation
-2.3.5 RadioButton (Radio Button) & Checkbox (Checkbox)
-2.3.6 ToggleButton and Switch
-2.3.7 ProgressBar (Progress Bar)
-2.3.9 RatingBar (Star Rating Bar)
-2.4.1 ScrollView (Scroll Bar)
-2.4.2 Date & Time Components (Part 1)
-2.4.3 Date & Time Components (Part 2)
-2.4.5 Simple Usage of ListView
-2.4.6 BaseAdapter Optimization
-2.4.8 Solving ListView Checkbox Misalignment Issue
-2.4.9 ListView Data Update Issue
-2.5.0 Building a Reusable Custom BaseAdapter
-2.5.1 Implementing Multi-Layout ListView Items
-2.5.2 Basic Usage of GridView (Grid View)
-2.5.3 Basic Usage of Spinner (List Option Box)
-2.5.4 Basic Usage of AutoCompleteTextView (Auto-Complete Text Box)
-2.5.5 Basic Usage of ExpandableListView (Collapsible List)
-2.5.6 Basic Usage of ViewFlipper (Flip View)
-2.5.8 Detailed Explanation of Notification (Status Bar Notification)
-2.5.9 Detailed Explanation of AlertDialog (Dialog Box)
-2.6.0 Basic Usage of Other Common Dialogs
-2.6.1 Basic Usage of PopupWindow (Floating Box)
-2.6.3 Simple Usage of ViewPager
-2.6.4 Simple Usage of DrawerLayout (Official Side Menu)
-3.1.1 Event Handling Mechanism Based on Listeners ```
- 3.2 Callback-Based Event Handling Mechanism
- 3.3 Analysis of Handler Message Passing Mechanism
- 3.4 TouchListener vs OnTouchEvent + Multi-Touch
- 3.5 Monitoring Content Changes in EditText
- 3.6 Responding to System Setting Events (Configuration Class)
- 3.7 AsyncTask Asynchronous Task
- 3.8 Gestures
- 4.1.1 Introduction to Activity
- 4.1.2 Getting Started with Activity
- 4.1.3 Advanced Activity
- 4.2.1 Introduction to Service
- 4.2.2 Intermediate Service
- 4.2.3 Advanced Service
- 4.3.1 Introduction to BroadcastReceiver
- 4.3.2 Advanced BroadcastReceiver
- 4.4.1 Introduction to ContentProvider
- 4.4.2 Advanced ContentProvider - Document Provider
- 4.5.1 Basic Usage of Intent
- 4.5.2 Passing Complex Data with Intent
- 5.1 Basic Overview of Fragment
- 5.2.1 Fragment Example - Bottom Navigation Bar Implementation (Method 1)
- 5.2.2 Fragment Example - Bottom Navigation Bar Implementation (Method 2)
- 5.2.3 Fragment Example - Bottom Navigation Bar Implementation (Method 3)
- 5.2.4 Fragment Example - Bottom Navigation Bar + ViewPager Page Sliding
- 5.2.5 Fragment Example - Simple Implementation of News/Shopping App List Fragment
- 6.1 Data Storage and Access - File Storage and Reading
- 6.2 Data Storage and Access - SharedPreferences for Saving User Preferences
- 6.3.1 Data Storage and Access - Introduction to SQLite Database
- 6.3.2 Data Storage and Access - Advanced SQLite Database
- 7.1.1 Android Network Programming and HTTP Protocol Learning
- 7.1.2 Learning Android HTTP Request and Response Headers
- 7.1.3 Android HTTP Request Method: HttpURLConnection
- 7.1.4 Android HTTP Request Method: HttpClient
- 7.2.1 Android XML Data Parsing
- 7.2.2 Android JSON Data Parsing
- 7.3.1 Android File Upload
- 7.3.2 Android File Download (1)
- 7.3.3 Android File Download (2)
- 7.4 Android Calling WebService
- 7.5.1 Basic Usage of WebView
- 7.5.2 WebView and JavaScript Interaction Basics
- 7.5.3 WebView Considerations After Android 4.4
- 7.5.4 WebView File Download
- 7.5.5 WebView Cache Issues
- 7.5.6 WebView Handling Webpage Error Codes
- 7.6.1 Socket Learning Network Basics Preparation
- 7.6.2 TCP Protocol Based Socket Communication (1)
- 7.6.3 TCP Protocol Based Socket Communication (2)
- 7.6.4 UDP Protocol Based Socket Communication
- 8.1.1 Summary of 13 Drawable Types in Android Part 1
- 8.1.2 Summary of 13 Drawable Types in Android Part 2
- 8.1.3 Summary of 13 Drawable Types in Android Part 3
- 8.2.1 Comprehensive Analysis of Bitmap Part 1
- 8.2.2 Bitmap-Induced OOM Issues
- 8.3.1 Detailed Explanation of Three Drawing Tools
- 8.3.2 Drawing Tool Practical Examples
- 8.3.3 Paint API - MaskFilter
- 8.3.4 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 1)
- 8.3.5 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 2)
- 8.3.6 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 3)
- 8.3.7 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 4)
- 8.3.8 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 5)
- 8.3.9 Paint API - ColorFilter (Color Filter) (1/3)
- 8.3.10 Paint API - ColorFilter (Color Filter) (2/3)
- 8.3.11 Paint API - ColorFilter (Color Filter) (3/3)
- 8.3.12 Paint API - PathEffect (Path Effect)
- 8.3.13 Paint API - Shader (Image Rendering)
- 8.3.14 Paint Enum/Constants and ShadowLayer Shadow Effect
- 8.3.15 Paint API - Typeface (Font Style)
- 8.3.16 Canvas API Detailed Explanation (Part 1)
- 8.3.17 Canvas API Detailed Explanation (Part 2) Clipping Methods Collection
- 8.3.18 Canvas API Detailed Explanation (Part 3) Matrix and drawBitmapMesh
- 8.4.1 Android Animation Collection - Frame Animation
- 8.4.2 Android Animation Collection - Tween Animation
- 8.4.3 Introduction to Property Animation in Android
- 8.4.4 Further Exploration of Property Animation in Android
- 9.1 Playing Sound Effects with SoundPool (Duang~)
- 9.2 Playing Audio and Video with MediaPlayer
- 9.3 Taking Photos with Camera
- 9.4 Recording Audio with MediaRecord
- 10.1 TelephonyManager (Phone Manager)
- 10.2 SmsManager (SMS Manager)
- 10.3 AudioManager (Audio Manager)
- 10.4 Vibrator (Vibrator)
- 10.5 AlarmManager (Alarm Service)
- 10.6 PowerManager (Power Service)
- 10.7 WindowManager (Window Management Service)
- 10.8 LayoutInflater (Layout Service)
- 10.9 WallpaperManager (Wallpaper Manager)
- 10.10 Sensor Topics (1) – Introduction
- 10.11 Sensor Topics (2) – Orientation Sensor
- 10.12 Sensor Topics (3) – Accelerometer/Gyroscope Sensor
- 10.12 Sensor Topics (4) – Understanding Other Sensors
- 10.14 Introduction to Android GPS
- 11.0 Completion of the 2015 Latest Android Basic Beginner's Tutorial
- 12.1 Android Practice: DrySister Viewing Girls App (First Edition) – Project Setup and Simple Implementation
- 12.2 DrySister Viewing Girls App (First Edition) – 2. Parsing Backend Data
- 12.3 DrySister Viewing Girls App (First Edition) – 3. Image Loading Optimization (Building a Small Image Cache Framework)
- 12.4 DrySister Viewing Girls App (First Edition) – 4. Adding Data Caching (Introducing SQLite)
- 12.5 DrySister Viewing Girls App (First Edition) – 5. Code Review, Adjustment, and Logging Class Writing
- 12.6 DrySister Viewing Girls App (First Edition) – 6. Icon Creation, Obfuscation, Signing, Packaging, APK Slimming, and App Release