APK 之间共享 SharedPreferences
关于数据存储,使用 ContentProvider 比使用 SharedPreferences 共享数据更安全些。
注意一下 SharedPreferences 的 apply() 与 commit() 的区别。
先在另外一个应用程序 B 里创建,然后在自己的应用 A 里访问。
SharedPreferences pref=getActivity().getSharedPreferences(Settings.PREFS_NAME, Context.MODE_WORLD_READABLE|Context.MODE_WORLD_WRITEABLE);
这里以可读可写的形式创建,否则 A 访问不到 B 中的 SharedPreferences。
查看权限文件 busybox ls -l /data/data/com.android.settings/shared_prefs/custom_preferences.xml
-rw-rw-rw- 1 1000 1000 321 May 6 02:10 /data/data/com.android.settings/shared_prefs/custom_preferences.xml
public static SharedPreferences getPreferences(Context context) { try { Context settings=context.createPackageContext("com.android.settings", Context.MODE_WORLD_READABLE); return settings.getSharedPreferences(PREFS_NAME, Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS); }catch(NameNotFoundException e) { e.printStackTrace(); Log.e(TAG,"404 Settings app not found"); return PreferenceManager.getDefaultSharedPreferences(context); } }
这里别忘记添加 Context.MODE_MULTI_PROCESS 属性(Gingerbread (Android 2.3)后需要显式声明)。如果没有的话,在 B 应用里修改 SharedPreferences 后将不会更新到 A 应用里,也就是获取的仍是旧值。
没有 USB 线,使用串口通过网络连接 ADB
在 Settings 应用里,进入以太网配置,点击 DHCP 获取动态 IP 地址。
minicom 终端输入命令,绑定端口号 5555,可以设置其他大的值(小于1024的端口号保留使用)
setprop service.adb.tcp.port 5555
stop adbd
start adbd
然后在终端输入命令 netcfg,将会获得如下内容
eth0 192.168.1.125
wlan 192.168.1.105 *(If wifi is up)
接着重启 adb 服务
adb kill-server
adb connect 192.1.168.125
adb devices
根据显示的结果,连接局域网使用 192.1.168.125,连接 Wi-Fi 使用 192.1.168.105
终端输入 adb 查看 connect 或其他命令的用法
然后就会显示所有的设备。
SQLite 批量数据处理
如果从网络下载 XML 解析后,可能需要向数据库增添大量数据。一条一条记录得添加会相当慢,不断的文件打开和读写。
可以设置为一次事务(transaction),先写入内存,然后一次提交更改,这样会快很多。
SQLiteDatabase db=this.getWritableDatabase(); db.beginTransaction(); db.insert(sql); // thousands of records db.setTransactionSuccessful(); db.endTransaction(); db.close();
使用 Browser 打开某 URL,避免创建多个 TAB 页面
这样是可行的,使用 EXTRA_APPLICATION_ID 标签可以保证 tab 的重用
Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
关于 Map 中 key/value 的遍历最有效的方式
如果仅对 key 感兴趣:
Map<String, Object> map = ...; for (String key : map.keySet()) { // ... }
如果只需要使用 value:
for (Object value : map.values()) { // ... }
或者程序需要获取 key 和 value
for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); // ... }
警告:如果在遍历的过程中有增删操作,需要使用 Iterator 实现,上面的方法不可取。
隐藏 Settings 应用中左边一列中的一项
如果注释掉相关代码,会很繁琐。隐藏不让显示就行了。
比如蓝牙功能没有实现,所以隐藏有关 Bluetooth 的设置。
Packages/app/Settings/src/com/android/settings/Settings.java 中有这么一句
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) { target.remove(header); }
继续溯源,追 frameworks/base/core/java/android/content/pm/PackageManager.java
public abstract boolean hasSystemFeature(String name);
抽象方法,实现在哪里呢?
frameworks/base/core/java/android/app/ApplicationPackageManager.java
@Override public boolean hasSystemFeature(String name) { try { return mPM.hasSystemFeature(name); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } }
mPM.hasSystemFeature(name) 经过 AIDL 实际上调用到文件 PackageManagerService.java
public boolean hasSystemFeature(String name) { synchronized (mPackages) { return mAvailableFeatures.containsKey(name); } }
else if ("feature".equals(name)) { String fname = parser.getAttributeValue(null, "name"); if (fname == null) { Slog.w(TAG, "<feature> without name at " + parser.getPositionDescription()); } else { //Log.i(TAG, "Got feature " + fname); FeatureInfo fi = new FeatureInfo(); fi.name = fname; mAvailableFeatures.put(fname, fi); } XmlUtils.skipCurrentTag(parser); continue; }
if ("feature".equals(name)) mAvailableFeatures.put(fname, fi);
mAvailableFeatures 里面的内容是通过读取 /system/etc/permissions/ 下面的文件。而这些文件是从源码路径
frameworks/base/data/etc/ 复制过来的,配置都在里面了,grep 一下 bluetooth,
然后注释掉其 feature 重新编译源码,或者先去掉 /system/etc/permissions/handheld_core_hardware.xml 中的
<feature name="android.hardware.bluetooth" /> 重启设备,看是否生效。
播放 assets 文件夹里音频的文件
使用酷狗音乐软件时,打开都会听到一个女声"Hello Kugou"。设想我们的问候语文件 greetings.mp3 存放在 Android 工程下的 asset 文件夹里。打开播放的代码如下:
AssetFileDescriptor afd = getAssets().openFd("grettings.mp3"); player = new MediaPlayer(); //player.setDataSource(afd.getFileDescriptor()); // PROBLEM: it starts playing all the audio files in the assets directory player.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength()); player.prepare(); player.start();
稍微注意一下上面的注释,你只是需要播放一个文件,而不是所有的音频文件。
关于 getprop 命令
需要获取某个 key 对应的 value,比如我之前的文章 Android 第三方 APK 包的静默安装 就用到了 getprop 命令。
getprop 命令的代码 grep 一下,在 system/core/ 目录,或者 locate getprop.c 来得更快。
获取局域网内自己的主机名: adb shell getprop("net.hostname") 返回 android-364366301cf266e6
关于设备名称的代码在这里 frameworks/base/services/java/com/android/server/ConnectivityService.java
// setup our unique device name if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { String id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); if (id != null && id.length() > 0) { String name = new String("android-").concat(id); SystemProperties.set("net.hostname", name); } }
这里提供两种方法调用 getprop,虽然都不怎么好。
1. 利用 Runtime 调用 Shell 命令获取执行结果。Runtime.exec(new String[]{"getprop","net.hostname"});
2. Android 其实提供了相关的 API 的(对应 android.os.SystemProperties),出于某某原因,对外隐藏(@hide)。
然后就归结到如何调用隐藏的 API 问题上了。之前的文章#获取屏幕的分辨率 就用过,就是采用 Java 的反射机制。
这样做有点危险,因为可能以后 Android 会移除该方法,然后程序异常中止,得到 NoSuchMethodException 。
参阅 Using internal (com.android.internal) and hidden (@hide) APIs ,这里有更详细的介绍,分 5 部分。
View 转位图
Android 的 SDK 自带可以截图的工具 hierarchyviewer,但是如果想在程序中获取某个 View 的图片并存储,可以这么做:
view.setDrawingCacheEnabled(true); bitmap = Bitmap.createBitmap(view.getDrawingCache()); // Save the bitmap wherever... view.setDrawingCacheEnabled(false);
使用 setDrawingCacheEnabled 方法打开/关闭来获取 drawing cache。
UTF-8 编码中 BOM 字符问题
UTF-8 编码中的 字节顺序标记 (BOM) 字符会引起的 gcc 编译出错,文件开头有无法识别的字符。
BOM(Byte Order Mark)需要批量删除,使用 grep + sed 命令即可完成。
martin@M2037:$ grep -rIlo $'^\xef\xbb\xf' . | xargs sed --in-place -e 's/\xef\xbb\xbf//'
grep -r 递归搜索 -I 排除二进制文件的搜索 -l 搜索符合条件的文件 -o 仅打印符合条件的行,分行打印
sed --in-place 编辑文件,提供扩展名的话会备份原文件 -e 执行的表达式。 s/\xef\xbb\xbf// 是将串 \xef\xbb\xbf 替换为空,也就是去掉。
一些文本编辑器会默认添加 BOM 头,比如 Windows 自带的记事本 Notepad。
用 Windows 上的 notepad 或者 Ubuntu 上的 gedit 打开是看不到的,用 Notepad++.exe 或者 vim 就可以查看得到。
BOM 有什么用呢?它是用来标记多字节构成的字符串时的字节序,如同 CPU 架构的大/小端模式样。
adb pull /data/app/some-app.apk <SOMEWHERE>
Google Play 无法单独下载 Android APP 安装包,只能在线安装。APK 安装到手机后,Android 系统会保存一份文件在 /data/app目录,带有数字后缀,名为“APK的包名-1.apk”或者“APK的包名-2.apk”。
在某个 Android 设备上安装后,很想分享到其他 Android 设备。我们需要提取出这个包,但是在没有root Android 设备时,是无法查看 /data/app/ 目录下的内容的。好在这个目录有other组可读权限的。
Android 有两个很有用的命令,分别是 am(管理 Activity 的)和 pm(管理 Pakcage 的),不是所谓的上午和下午喔。 进入 adb shell 后,输入可以查看相信的使用信息。
pm list packages: prints all packages, optionally only
those whose package name contains the text in FILTER. Options:
-f: see their associated file.
-d: filter to only show disbled packages.
-e: filter to only show enabled packages.
-s: filter to only show system packages.
-3: filter to only show third party packages.
-i: see the installer for the packages.
-u: also include uninstalled packages.
使用 pm list package -f 可以获取所有包对应的文件名,用 grep 过滤一下,想要拽出的APK包的包名或关键字。
$ adb shell pm list package -f | grep cloudream
package:/data/app/com.cloudream.ishow-1/base.apk=com.cloudream.ishow
$ adb pull /data/app/com.cloudream.ishow-1/base.apk
就可以提取出来啦,当然,也可以提取 /system/app/ 下面的包哦。
C:\Users\Administrator\Desktop>adb pull /system/app/Magnifier/Magnifier.apk
[100%] /system/app/Magnifier/Magnifier.apk