11 เมษายน 2558

[Android Dev Tips] วิธีการดึงไฟล์ฐานข้อมูลจากเครื่องจริง




        บ่อยครั้งที่เวลาเขียนแอพซักตัวที่มีการสร้างฐานข้อมูลขึ้นมา ก็มักจะต้องมีการลองดึงไฟล์ฐานข้อมูลมาเช็คบนคอมเพื่อตรวจสอบว่าโค๊ดทำงานได้อย่างถูกต้องหรือไม่

        และผู้ที่หลงเข้ามาอ่านหลายๆคนก็มักจะประสบปัญหาดึงไฟล์ฐานข้อมูลจากแอพบนอุปกรณ์แอนดรอยด์ออกมาดูบนคอมไม่ได้ ซึ่งเป็นปัญหาที่เกิดขึ้นกับมือใหม่แทบจะทุกคน ดังนั้นเจ้าของบล็อกขอจัดบทความสั้นๆสำหรับเรื่องนี้เสียหน่อยดีกว่า

        เป็นที่รู้กันอยู่แล้วว่าไฟล์ฐานข้อมูลของแต่ละแอพจะถูกเก็บไว้ในโฟลเดอร์ของแอพตัวเอง

        /data/data/<package_name>/databases/

        และส่วนใหญ่นั้นจะใช้วิธีดึงฐานข้อมูลออกมาโดยใช้ File Explorer ที่อยู่ใน DDMS หรือ Android Device Monitor แล้วเข้าไปยัง Path ดังกล่าว




        แต่ทว่าก็มีผู้ที่หลงเข้ามาอ่านหลายๆคนเจอปัญหาว่าเปิดโฟลเดอร์ data ไม่ได้



ก็ดึงข้อมูลได้ปกตินี่ ไม่เห็นจะมีอะไร


        สำหรับผู้ที่หลงเข้ามาอ่านที่ยังไม่เข้าใจว่าเปิดโฟลเดอร์ data ไม่ได้มันเป็นยังไง ให้ลองต่อกับอุปกรณ์แอนดรอยด์ของจริง แล้วใช้ Android Device Monitor หรือ DDMS เปิดโฟลเดอร์ดังกล่าวในเครื่องนั้นๆดู

        เพราะว่าเครื่องที่เปิดโฟลเดอร์นั้นได้ จะมีแต่ Emulator เท่านั้น


เคยสังเกตกันมั้ย?

        เวลาเปิด Android Device Monitor หรือ DDMS ตรงรายชื่ออุปกรณ์แอนดรอยด์ที่เชื่อมต่ออยู่ เฉพาะ Emulator เท่านั้นที่ตรงช่องเลขเวอร์ชันจะมีต่อท้ายว่า debug


        ซึ่งเป็นการบอกให้รู้ว่าอุปกรณ์แอนดรอยด์ตัวนั้นๆเป็น Debug Device หรือมีไว้สำหรับทดสอบเท่านั้น แต่ทว่าเครื่องที่ผู้ที่หลงเข้ามาอ่านใช้ๆกันอยู่นั้น เป็น Production Device ไม่ใช่เครื่องทดสอบซักหน่อย (ก็ซื้อมาจากร้านค้าที่วางขายตามท้องตลาด)

        ซึ่ง Debug Device จะมีความพิเศษตรงที่สามารถเข้าถึงไฟล์ได้ทุกที่ภายในเครื่อง (เพราะว่ามันใช้ทดสอบนั่นเอง) แต่ Production Device จะไม่สามารถเข้าถึงได้ เพื่อป้องกันอันตรายที่อาจจะเกิดขึ้นได้ ดังนั้นจึงทำให้เปิดเข้าไปยังโฟลเดอร์ดังกล่าวไม่ได้

แล้วจะแก้ปัญหายังไงดี?

        วิธีแก้ปัญหาจะมีอยู่ด้วยกันสองวิธี คือ

วิธีแรก - Root เครื่องซะสิ

        เมื่อปัญหาที่แท้จริงนั้นเกิดจากเรื่องความปลอดภัย ถ้างั้นก็ลองแหกความปลอดภัยดูสิ เพราะการ Root เครื่องจะทำให้สามารถเข้าถึงระดับ Root ได้ จึงสามารถเข้าถึงไฟล์ได้ทั้งเครื่อง เหมือนกับ Debug Device

        พอ Root เรียบร้อยแล้วก็จะสามารถใช้แอพ File Explorer ที่รองรับ Root Access อย่าง ES File Explorer เพื่อเข้าไปก๊อปไฟล์ตามที่ต้องการได้เลย แต่ว่าต้องก๊อปผ่านแอพบนเครื่องแอนดรอยด์เท่านั้น แล้วต้องก๊อปลงคอมพิวเตอร์อีกที




วิธีที่สอง - เขียนโค๊ดให้ก๊อปไฟล์ออกมา

        เนื่องจากโฟลเดอร์ดังกล่าวถูกป้องกันไม่ให้เข้าถึงได้ แต่ก็อย่าลืมว่าแอพที่เป็นเจ้าของโฟลเดอร์นั้นๆจะเข้าถึงโฟลเดอร์ได้อย่างปกติ ดังนั้นโค๊ดคำสั่งใดๆก็ตามที่อยู่ในแอพนั้น จะสามารถเข้าถึงโฟลเดอร์นั้นได้ทั้งหมด

        ดังนั้นก็ไปเขียนให้แอพนั้นมันดึงฐานข้อมูลออกมาเองเลยสิ!!!

public boolean exportDatabaseFile(Context context, String dbName) {
    try {
        File dbFile = context.getDatabasePath(dbName);
        File exportFile = new File(Environment.getExternalStorageDirectory() + "/" + dbName);
        FileInputStream fileInputStream = new FileInputStream(dbFile);
        FileOutputStream fileOutputStream = new FileOutputStream(exportFile);
        FileChannel fileInputChannel = fileInputStream.getChannel();
        FileChannel fileOutputChannel = fileOutputStream.getChannel();
        fileInputChannel.transferTo(0, fileInputChannel.size(), fileOutputChannel);
        fileInputStream.close();
        fileOutputStream.close();
        return true;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
}

        นี่คือคำสั่งก๊อปไฟล์จากฐานข้อมูลของแอพนั้นๆมาเก็บไว้ใน External Storage โดยเวลาใช้งานจริงๆจะเป็นแบบนี้

        ก่อนอื่นต้องสมมติไฟล์ Database Helper ขึ้นมาก่อน

MyDbHelper.java
package com.akexorcist.exportdatabase;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

class MyDbHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "bakery_store";
    private static final int DB_VERSION = 1;
    public static final String TABLE_NAME = "product";
    public static final String COL_NAME = "name";
    public static final String COL_PIECE_PRICE = "piece_price";
    public static final String COL_CAKE_PRICE = "cake_price";

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

    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE " + TABLE_NAME +" (_id INTEGER PRIMARY KEY AUTOINCREMENT, "
                + COL_NAME + " TEXT, " + COL_PIECE_PRICE + " INTEGER, " + COL_CAKE_PRICE + " INTEGER);");
        db.execSQL("INSERT INTO " + TABLE_NAME + " (" + COL_NAME + ", " + COL_PIECE_PRICE
                + ", " + COL_CAKE_PRICE + ") VALUES ('Chocolate Fudge', 95, 750);");
        db.execSQL("INSERT INTO " + TABLE_NAME + " (" + COL_NAME + ", " + COL_PIECE_PRICE
                + ", " + COL_CAKE_PRICE + ") VALUES ('Banana Choc Cake', 55, 500);");
        db.execSQL("INSERT INTO " + TABLE_NAME + " (" + COL_NAME + ", " + COL_PIECE_PRICE
                + ", " + COL_CAKE_PRICE + ") VALUES ('Banoffee', 75, 700);");
        db.execSQL("INSERT INTO " + TABLE_NAME + " (" + COL_NAME + ", " + COL_PIECE_PRICE
                + ", " + COL_CAKE_PRICE + ") VALUES ('Cheese Cake', 85, 800);");
        db.execSQL("INSERT INTO " + TABLE_NAME + " (" + COL_NAME + ", " + COL_PIECE_PRICE
                + ", " + COL_CAKE_PRICE + ") VALUES ('Tiramisu', 85, 800);");
    }

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

        ใน Activity ที่ใช้งานฐานข้อมูลก็จะมีการเพิ่มคำสั่งก๊อปไฟล์ฐานข้อมูลเข้าไปด้วย (คืออยากก๊อปไฟล์ตอนไหนก็ใส่ตอนนั้นน่ะแหละ)

MainActivity.java
package com.akexorcist.exportdatabase;

import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;


public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyDbHelper helper = new MyDbHelper(this);
        helper.getWritableDatabase();
        exportDatabaseFile(this, "bakery_store");
    }

    public boolean exportDatabaseFile(Context context, String dbName) {
        try {
            File dbFile = context.getDatabasePath(dbName);
            File exportFile = new File(Environment.getExternalStorageDirectory() + "/" + dbName);
            FileInputStream fileInputStream = new FileInputStream(dbFile);
            FileOutputStream fileOutputStream = new FileOutputStream(exportFile);
            FileChannel fileInputChannel = fileInputStream.getChannel();
            FileChannel fileOutputChannel = fileOutputStream.getChannel();
            fileInputChannel.transferTo(0, fileInputChannel.size(), fileOutputChannel);
            fileInputStream.close();
            fileOutputStream.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
}

        จากนั้นก็เปิดแอพซะ แล้วให้คำสั่งดังกล่าวมันทำงาน แล้วค่อยไปเปิดไฟล์ในเครื่องเพื่อโอนลงคอมพิวเตอร์

        สรุปสั้นๆ - ใส่โค๊ดให้แอพมันก๊อปไฟล์ฐานข้อมูลของมันเองไว้ที่ External Storage จะได้ก๊อปลงคอมพิวเตอร์ได้ (ต้องแอพที่เป็นเจ้าของโฟลเดอร์เท่านั้น ไม่สามารถไปก๊อปของแอพตัวอื่นได้)


        เพียงเท่านี้ก็ได้ไฟล์ฐานข้อมูลมาเปิดดูบนคอมพิวเตอร์แล้ว



        สำหรับผู้ที่หลงเข้ามาอ่านคนใดต้องการไฟล์ตัวอย่างสามารถดาวน์โหลดได้จาก
        • Sleeping For Less
        • Google Drive




เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium