我们今天分析下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])