我的:https://www.jianshu.com/u/c91e642c4d90
我的CSDN:http://blog.csdn.net/wo_ha
我的GitHub:https://github.com/chuanqiLjp
转载请注明出处:https://www.jianshu.com/p/b0b541b94427
序言
上一篇Android 自定义字体,更换系统默认显示的字体使用自定义字体有讲到怎样指定控件显示指定字体,怎样整个软件显示指定字体,怎样WebView加载指定字体,但是还留下一个怎样修改整个系统的默认字体,由于内容较多,所以单独抽离出来讲,由于要操作系统文件,因此需要Root权限或系统签名,自己在操作前建议先备份下字体配置文件/system/etc/system_fonts.xml和/system/etc/fallback_fonts.xml,否则操作失败有可能开机后无法进入桌面,此时就需要将备份的system_fonts.xml推送到对应目录下并修改为对应的权限。
1、字体加载原理
-
① Android 系统的字体文件:位于 /system/fonts/ 文件夹下,我们可以到对应的目录下进行查看,可以看出,Android的字体文件都是ttf文件,命令顺序:adb shell ——>cd /system/fonts/ ——>ll,查看结果如下图所示:
- ② 在/system/etc/目录下有两个字体配置文件,分别是system_fonts.xml 和 fallback_fonts.xml ,当系统需要加载字体时,会优先从 system_fonts.xml 文件开始查找,如果没有找到再进入 fallback_fonts.xml 查找。
system_fonts.xml示范文件
sans-serif
arial
helvetica
tahoma
verdana
Roboto-Regular.ttf
Roboto-Bold.ttf
Roboto-Italic.ttf
Roboto-BoldItalic.ttf
sans-serif-light
Roboto-Light.ttf
Roboto-LightItalic.ttf
sans-serif-thin
Roboto-Thin.ttf
Roboto-ThinItalic.ttf
sans-serif-condensed
RobotoCondensed-Regular.ttf
RobotoCondensed-Bold.ttf
RobotoCondensed-Italic.ttf
RobotoCondensed-BoldItalic.ttf
serif
times
times new roman
palatino
georgia
baskerville
goudy
fantasy
cursive
ITC Stone Serif
DroidSerif-Regular.ttf
DroidSerif-Bold.ttf
DroidSerif-Italic.ttf
DroidSerif-BoldItalic.ttf
Droid Sans
DroidSans.ttf
DroidSans-Bold.ttf
monospace
courier
courier new
monaco
DroidSansMono.ttf
fallback_fonts.xml 示范文件
DroidNaskh-Regular.ttf
DroidNaskhUI-Regular.ttf
DroidSansEthiopic-Regular.ttf
DroidSansHebrew-Regular.ttf
DroidSansHebrew-Bold.ttf
NotoSansThai-Regular.ttf
NotoSansThai-Bold.ttf
NotoSansThaiUI-Regular.ttf
NotoSansThaiUI-Bold.ttf
DroidSansArmenian.ttf
DroidSansGeorgian.ttf
NotoSansDevanagari-Regular.ttf
NotoSansDevanagari-Bold.ttf
NotoSansDevanagariUI-Regular.ttf
NotoSansDevanagariUI-Bold.ttf
NotoSansTamil-Regular.ttf
NotoSansTamil-Bold.ttf
NotoSansTamilUI-Regular.ttf
NotoSansTamilUI-Bold.ttf
NotoSansMalayalam-Regular.ttf
NotoSansMalayalam-Bold.ttf
NotoSansMalayalamUI-Regular.ttf
NotoSansMalayalamUI-Bold.ttf
NotoSansBengali-Regular.ttf
NotoSansBengali-Bold.ttf
NotoSansBengaliUI-Regular.ttf
NotoSansBengaliUI-Bold.ttf
NotoSansTelugu-Regular.ttf
NotoSansTelugu-Bold.ttf
NotoSansTeluguUI-Regular.ttf
NotoSansTeluguUI-Bold.ttf
NotoSansKannada-Regular.ttf
NotoSansKannada-Bold.ttf
NotoSansKannadaUI-Regular.ttf
NotoSansKannadaUI-Bold.ttf
NotoSansKhmer-Regular.ttf
NotoSansKhmer-Bold.ttf
NotoSansKhmerUI-Regular.ttf
NotoSansKhmerUI-Bold.ttf
NotoSansLao-Regular.ttf
NotoSansLao-Bold.ttf
NotoSansLaoUI-Regular.ttf
NotoSansLaoUI-Bold.ttf
NanumGothic.ttf
Padauk-book.ttf
Padauk-bookbold.ttf
NotoSansSymbols-Regular.ttf
AndroidEmoji.ttf
NotoColorEmoji.ttf
DroidSansFallback.ttf
MTLmr3m.ttf
2、为系统添加一个默认字体
修改系统默认字体的原理:根据系统字体加载原理可知,我们只需要在路径 /system/fonts/ 下添加我们自定义的ttf字体文件,然后修改 /system/etc/system_fonts.xml 字体配置文件,按照响应的格式添加一个节点,由于需要系统默认使用该字体,因此该节点需要是根节点familyset下的第一个子节点,系统在system_fonts.xml中找到了该字体的配置,故不会去fallback_fonts.xml 寻找,因此也只需要修改这一个配置文件即可,文件修改成功后需要注意已修改文件的读写权限(否则会没有效果),为了方便,我们设置全部用户可读可写。
① 判断系统字体库是否已有需要添加的字体文件
/**
* 检查字体文件是否存在系统目录中
* @param fontName 字体文件名称
* @return ture:已存在
*/
private boolean checkFontTTFFile(String fontName) {
File dir = new File(systemFontsDir);
File[] files = dir.listFiles(); //字库列表
if (files != null && files.length > 0) {
for (int i = 0; i < files.length; i++) {
File file = files[i];
if (file.exists() && file.getName().equals(fontName)) {
return true;
}
}
}
return false;
}
② 若①判断后没有则需要拷贝字体文件到对应路径下
/**
* 拷贝字体文件
*
* @param fontName 字体文件名
* @param destDir 目标目录
*/
private void copyFontTTF(String fontName, String destDir) {
try {
//将Assets文件夹中的字体文件读出来
InputStream inputStream = getAssets().open("fonts/" + fontName);
//将字体文件写入到sd卡中 ,不能直接写入 /system/fonts/下,因为没有写文件的权限
File file = new File(getCacheDir(), fontName);
FileOutputStream fos = new FileOutputStream(file);
int leng = 0;
byte[] buffer = new byte[1024];
while (-1 != (leng = inputStream.read(buffer))) {
fos.write(buffer, 0, leng);
}
fos.flush();
fos.close();
inputStream.close();
runRootCommand("cp " + file.getAbsolutePath() + " " + destDir); //使用命令进行文件拷贝
Log.e(TAG, "copyFontTTF: 字体文件拷贝成功");
} catch (Exception e) {
e.printStackTrace();
}
}
③ 判断字体配置文件 system_fonts.xml 是否已有该节点,若有则不进行操作,若无进行步骤④
/**
* 检查指定的XML文件中是否有节点名称为 refNodeName 的值为 compareNodeValue 的节点
*
* @param refNodeName 参考的节点名称
* @param compareNodeValue 被比较的节点名称下的值
* @param file XML文件
* @return ture存在该节点
*/
private boolean checkXmlNode(String refNodeName, String compareNodeValue, File file) {
boolean result = false;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file); // 解析XML到内存中
NodeList nodeList = doc.getElementsByTagName(refNodeName);
for (int i = 0; i < nodeList.getLength(); i++) { //判断有没有该节点
Node item = nodeList.item(i);
String itemValue = item.getFirstChild().getNodeValue();
if (itemValue.equals(compareNodeValue)) {
result = true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
Log.e(TAG, "checkXmlNode: 检查是否存在该节点 refNodeName=" + refNodeName + ",compareNodeValue=" + compareNodeValue + ",result=" + result);
return result;
}
④ 若③中没有该节点,则在根节点familyset下的第一位置添加该节点并保存
/**
* 向system_fonts.xml文件增加一个节点
*
* @param nodeValue 节点的值
*/
private void addSystemFontNote(String nodeValue) {
try {
File file = new File(system_fonts);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file); // 解析XML到内存中
Node nodeFamilyset = doc.getElementsByTagName("familyset").item(0);
Element elementFamily = doc.createElement("family");
Element elementNameset = doc.createElement("nameset");
Element elementName = doc.createElement("name");
elementName.setTextContent(nodeValue);
elementNameset.appendChild(elementName);//将name节点设置为nameset的子节点
Element elementFileset = doc.createElement("fileset");
Element elementFile = doc.createElement("file");
elementFile.setTextContent(nodeValue + ".ttf");
elementFileset.appendChild(elementFile);
elementFamily.appendChild(elementNameset);
elementFamily.appendChild(elementFileset);
Node nodeFamily = doc.getElementsByTagName("family").item(0);
nodeFamilyset.insertBefore(elementFamily, nodeFamily); //将节点elementFamily插入到节点nodeFamily之前
// 保存到文件中
saveXmlFile(doc, system_fonts);
Log.e(TAG, "addSystemFontNote: 节点添加成功");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 保存xml文件到指定路径
* @param doc 要保存的XML文档对象
* @param destFile 目标路径
* @throws TransformerException
*/
private void saveXmlFile(Document doc, String destFile) throws TransformerException {
Transformer transformer = TransformerFactory.newInstance().newTransformer();//创建一个用来转换DOM对象的工厂对象并获得转换器对象
DOMSource domSource = new DOMSource(doc); //定义要转换的源对象
File temFile = new File(getCacheDir(), "tem.xml");
StreamResult streamResult = new StreamResult(temFile); //定义要转换到的目标文件
transformer.transform(domSource, streamResult); //开始转换 ,
// runRootCommand("rm " + system_fonts);
runRootCommand("cat " + temFile.getAbsolutePath() + " > " + destFile);//复制文件内容
Log.e(TAG, "saveXmlFile: 保存文件成功," + destFile);
}
⑤ 完整的调用流程
private String system_fonts = "/system/etc/system_fonts.xml";
private String fallback_fonts = "/system/etc/fallback_fonts.xml";
private String systemFontsDir = "/system/fonts/";//系统存放字体文件的路径
public void ywsflsjtForSystem(View view) {
String fontName = "ywsflsjt.ttf";
runRootCommand("mount -o remount rw /system"); //重新挂载该路径为可读写模式
//以下操作建议在线程中执行
if (!checkFontTTFFile(fontName)) {
copyFontTTF(fontName, systemFontsDir);
}
runRootCommand("chmod 777 " + new File(systemFontsDir, fontName).getAbsolutePath());//修改字体文件的权限
String compareNodeValue = fontName.split("\\.")[0];
boolean node = checkXmlNode("name", compareNodeValue, new File(system_fonts));
if (node == false) { //没有该节点,在头部插入该节点
addSystemFontNote(compareNodeValue);
runRootCommand("chmod 777 " + system_fonts);
}
runRootCommand("reboot"); //要使修改后的字体生效需要重启系统
}
⑥ 运行Root指令的方法
/**
* 请求ROOT权限后执行命令(最好开启一个线程)
*
* @param cmd (pm install -r *.apk)
* @return
*/
public boolean runRootCommand(String cmd) {
Process process = null;
DataOutputStream os = null;
BufferedReader br = null;
StringBuilder sb = null;
try {
process = Runtime.getRuntime().exec("su");
os = new DataOutputStream(process.getOutputStream());
os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
br = new BufferedReader(new InputStreamReader(
process.getInputStream()));
sb = new StringBuilder();
String temp = null;
while ((temp = br.readLine()) != null) {
sb.append(temp + "\n");
if ("Success".equalsIgnoreCase(temp)) {
return true;
}
}
process.waitFor();
} catch (Exception e) {
Log.e(TAG, "异常:" + e.getMessage());
} finally {
try {
if (os != null) {
os.flush();
os.close();
}
if (br != null) {
br.close();
}
process.destroy();
} catch (Exception e) {
return false;
}
}
return false;
}
3、删除已添加的系统默认字体
和添加字体相对应,需要先删除字体文件,然后再删除 system_fonts.xml和fallback_fonts.xml两文件中的对应节点,由于我们没有修改过fallback_fonts.xml文件因此不需要做删除操作
① 删除节点是需要调用checkXmlNode方法坚持节点是否存在,若存在则删除数据
/**
* 删除 system_fonts.xml文件中的节点名称name值为 nodeValue的节点
*
* @param nodeValue
*/
private void deleteSystemFontNote(String nodeValue) {
Log.e(TAG, "deleteSystemFontNote: 删除文件" + system_fonts + "中的节点:" + nodeValue);
try {
File file = new File(system_fonts);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file); // 解析XML到内存中
NodeList nodeListName = doc.getElementsByTagName("name");
for (int i = 0; i < nodeListName.getLength(); i++) {
Node item = nodeListName.item(i);
String value = item.getFirstChild().getNodeValue();
if (value != null && nodeValue.equals(value)) {
Node nodenameset = item.getParentNode();
Node nodefamily = nodenameset.getParentNode();
Node nodefamilyset = nodefamily.getParentNode();
nodefamilyset.removeChild(nodefamily);
saveXmlFile(doc, system_fonts);
Log.e(TAG, "删除节点成功,nodeValue=" + nodeValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
② 完整的删除流程
public void deleteYwsflsjtForSystem(View view) {
String fontName = "ywsflsjt.ttf";
String nodeName = fontName.split("\\.")[0];
runRootCommand("mount -o remount rw /system"); //重新挂载该路径为可读写模式
runRootCommand("rm " + systemFontsDir + fontName); //删除字体文件
if (checkXmlNode("name", nodeName, new File(system_fonts))) {
deleteSystemFontNote(nodeName);
runRootCommand("chmod 777 " + system_fonts);
}
runRootCommand("reboot"); //要使修改后的字体生效需要重启系统
}
我的CSDN博客:http://blog.csdn.net/wo_ha/article/details/79202632