我們今天分析下android debug database 的源碼:
項目地址:
https://github.com/amitshekhariitbhu/Android-Debug-Database
具體作用:
可以在瀏覽器裏面,觀看編輯數據庫以及SP.
如何做到android 無侵入初始化
public class DebugDBEncryptInitProvider extends ContentProvider {
public DebugDBEncryptInitProvider() {
}
@Override
public boolean onCreate() {
DebugDB.initialize(getContext(), new DebugDBEncryptFactory());
return true;
}
我們看到,很聰明的使用了android 啓動 會調用ContentProvider onCreate 這一現象 ,也就是你直接引入包即可,不用寫任何的install 代碼。
使用ServerSocket 監聽端口
public class ClientServer implements Runnable {
private static final String TAG = "ClientServer";
private final int mPort;
private final RequestHandler mRequestHandler;
private boolean mIsRunning;
private ServerSocket mServerSocket;
public ClientServer(Context context, int port, DBFactory dbFactory) {
mRequestHandler = new RequestHandler(context, dbFactory);
mPort = port;
}
public void start() {
mIsRunning = true;
new Thread(this).start();
}
public void stop() {
try {
mIsRunning = false;
if (null != mServerSocket) {
mServerSocket.close();
mServerSocket = null;
}
} catch (Exception e) {
Log.e(TAG, "Error closing the server socket.", e);
}
}
@Override
public void run() {
try {
mServerSocket = new ServerSocket(mPort);
while (mIsRunning) {
Socket socket = mServerSocket.accept();
mRequestHandler.handle(socket);
socket.close();
}
} catch (SocketException e) {
// The server was stopped; ignore.
Log.e(TAG, "Web SocketException.", e);
} catch (IOException e) {
Log.e(TAG, "Web server error.", e);
} catch (Exception ignore) {
Log.e(TAG, "Exception.", ignore);
}
}
public void setCustomDatabaseFiles(HashMap<String, Pair<File, String>> customDatabaseFiles) {
mRequestHandler.setCustomDatabaseFiles(customDatabaseFiles);
}
public void setInMemoryRoomDatabases(HashMap<String, SupportSQLiteDatabase> databases) {
mRequestHandler.setInMemoryRoomDatabases(databases);
}
public boolean isRunning() {
return mIsRunning;
}
}
這一塊是socket 編程相關代碼。沒有什麼太大的技術難度,但是也是很有用的一塊api,沒有練習過的,可以看下,自己聯繫下。紙上得來終覺淺。
具體處理業務看:
public void handle(Socket socket) throws IOException {
BufferedReader reader = null;
PrintStream output = null;
try {
String route = null;
// Read HTTP headers and parse out the route.
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while (!TextUtils.isEmpty(line = reader.readLine())) {
if (line.startsWith("GET /")) {
int start = line.indexOf('/') + 1;
int end = line.indexOf(' ', start);
route = line.substring(start, end);
break;
}
}
// Output stream that we send the response to
output = new PrintStream(socket.getOutputStream());
if (route == null || route.isEmpty()) {
route = "index.html";
}
byte[] bytes;
Log.d("RequestHandler",route);
LogFile.d("RequestHandler",route);
if (route.startsWith("getDbList")) {
final String response = getDBListResponse();
bytes = response.getBytes();
} else if (route.startsWith("getAllDataFromTheTable")) {
final String response = getAllDataFromTheTableResponse(route);
bytes = response.getBytes();
} else if (route.startsWith("getTableList")) {
final String response = getTableListResponse(route);
bytes = response.getBytes();
} else if (route.startsWith("addTableData")) {
final String response = addTableDataAndGetResponse(route);
bytes = response.getBytes();
} else if (route.startsWith("updateTableData")) {
final String response = updateTableDataAndGetResponse(route);
bytes = response.getBytes();
} else if (route.startsWith("deleteTableData")) {
final String response = deleteTableDataAndGetResponse(route);
bytes = response.getBytes();
} else if (route.startsWith("query")) {
final String response = executeQueryAndGetResponse(route);
bytes = response.getBytes();
} else if (route.startsWith("deleteDb")) {
final String response = deleteSelectedDatabaseAndGetResponse();
bytes = response.getBytes();
} else if (route.startsWith("downloadDb")) {
bytes = Utils.getDatabase(mSelectedDatabase, mDatabaseFiles);
} else {
bytes = Utils.loadContent(route, mAssets);
}
if (null == bytes) {
writeServerError(output);
return;
}
// Send out the content.
output.println("HTTP/1.0 200 OK");
output.println("Content-Type: " + Utils.detectMimeType(route));
if (route.startsWith("downloadDb")) {
output.println("Content-Disposition: attachment; filename=" + mSelectedDatabase);
} else {
output.println("Content-Length: " + bytes.length);
}
output.println();
output.write(bytes);
output.flush();
} finally {
try {
if (null != output) {
output.close();
}
if (null != reader) {
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
其實就是去讀流,然後處理完結果,輸出流。
asset下面存放的是我們的資源,也就是html,css,js 等
這一塊相關的,有興趣可以自己研究下。相當於html 的編寫。
有關css的加載:
else {
bytes = Utils.loadContent(route, mAssets);
}
public static byte[] loadContent(String fileName, AssetManager assetManager) throws IOException {
InputStream input = null;
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
input = assetManager.open(fileName);
byte[] buffer = new byte[1024];
int size;
while (-1 != (size = input.read(buffer))) {
output.write(buffer, 0, size);
}
output.flush();
return output.toByteArray();
} catch (FileNotFoundException e) {
return null;
} finally {
try {
if (null != input) {
input.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
都是根據文件名讀取asset 下的東西。
從build.gradle 裏面讀取string:
buildTypes {
debug {
resValue("string", "PORT_NUMBER", "8080")
resValue("string", "DB_PASSWORD_PERSON", "a_password")
}
使用的時候:
portNumber = Integer.valueOf(context.getString(R.string.PORT_NUMBER));
可是BuildConfig 完全可以替代,不過這也是一條路
獲取手機ip地址:
public static String getAddressLog(Context context, int port) {
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
@SuppressLint("DefaultLocale")
final String formattedIpAddress = String.format("%d.%d.%d.%d",
(ipAddress & 0xff),
(ipAddress >> 8 & 0xff),
(ipAddress >> 16 & 0xff),
(ipAddress >> 24 & 0xff));
return "Open http://" + formattedIpAddress + ":" + port + " in your browser";
}
怎麼拿到數據庫的?用文件遍歷嗎?
Context context.databaseList()
android 有提供接口
怎麼拿到所有的sp? 遍歷嗎?
public static List<String> getSharedPreferenceTags(Context context) {
ArrayList<String> tags = new ArrayList<>();
String rootPath = context.getApplicationInfo().dataDir + "/shared_prefs";
File root = new File(rootPath);
if (root.exists()) {
for (File file : root.listFiles()) {
String fileName = file.getName();
if (fileName.endsWith(PREFS_SUFFIX)) {
tags.add(fileName.substring(0, fileName.length() - PREFS_SUFFIX.length()));
}
}
}
Collections.sort(tags);
return tags;
}
是的,只能遍歷。
sp 裏面的數據,是自己解析xml?
SharedPreferences preferences = context.getSharedPreferences(tag, Context.MODE_PRIVATE);
Map<String, ?> allEntries = preferences.getAll();
if (entry.getValue() instanceof String) {
valueColumnData.dataType = DataType.TEXT;
} else if (entry.getValue() instanceof Integer) {
我們看到,map<String,?> 雖然我們不知道類型,但是我們可以用這種形式。而且不需要自己解析,android 有先有的api.
怎麼查詢數據庫表的所有列名:
if (tableName != null) {
final String pragmaQuery = "PRAGMA table_info(" + quotedTableName + ")";
tableData.tableInfos = getTableInfo(db, pragmaQuery);
}
上面的sql 即可,最終SQl 想這種:
PRAGMA table_info([cars])