Android学习 问题与答案 (2)

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



原文地址:https://www.cnblogs.com/Martinium/p/android_learning2.html