先建立一个android 工程,首先我们设计好用户接口界面,根据设计,搭建的框架大概流程是 :
1.一个listview 展示某个目录先的字体文件。
2.点击listview某个Item的时候,将所选的字体文件数据拷贝到一个自己预先固定好的字体文件test.ttf.
3.请求Init进程执行脚本:脚本的作用是将固定好的test.ttf文件内容拷贝到系统默认字体以实现字体替换。
首先第一个的实现:
我需要定义个字体类来描述替换字体的信息,内容如下
package com.boyue.fontmanager;
/**
*
* @description: font information.
* @author: guo
* @time: Dec 28, 2015 11:56:29 AM
*/
public class Font {
/**
* The Name of the font.
*/
private String mFontName;
/**
* The path of font.
*/
private String mFontPath;
/**
* The id of font.
*/
private String mFontId;
/**
* the font is checked or not.
*/
private boolean isChecked;
public String getmFontName() {
return mFontName;
}
public void setmFontName(String mFontName) {
this.mFontName = mFontName;
}
public String getmFontPath() {
return mFontPath;
}
public void setmFontPath(String mFontPath) {
this.mFontPath = mFontPath;
}
public String getmFontId() {
return mFontId;
}
public void setmFontId(String mFontId) {
this.mFontId = mFontId;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
}
主要有四个信息:
1.字体名 2.字体所在路径 3.字体标志 4.字体是否已被选择
定义完这个类,我需要扫描制定路径的字体文件,为此我设计一个工具类来处理,具体如下:
package com.boyue.fontmanager.util;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InterruptedIOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import android.util.Log;
/**
*
* @function: TODO
* @author: guo
* @date: Mar 30, 2016 11:54:09 AM
*
*/
public class SearchFile {
private static final String TAG = "SearchFile";
public static final String[] filterFileExtension = new String[] { "ttf","ttc","otf"};
private final HashMap<File, Set<File>> dirMap = new HashMap<File, Set<File>>();
/**
*
* @function: TODO
* @author: guo
* @param path
* @return
*/
public LinkedList<File> scanFont(File path) {
LinkedList<File> files;
try {
recurseDir(path, getFilter());
} catch (InterruptedIOException e) {
Log.d(TAG, Log.getStackTraceString(e));
} finally {
files = dirMapToFile(dirMap);
}
return files;
}
/**
*
* @function: TODO
* @author: guo
* @param root
* @param filenameFilter
* @throws InterruptedIOException
*/
private void recurseDir(File root, FilenameFilter filenameFilter)
throws InterruptedIOException {
Set<File> fileSet = new HashSet<File>();
File[] subFile = root.listFiles(filenameFilter);
if (subFile != null) {
for (File item : subFile) {
if (item.isDirectory()) {
recurseDir(item, filenameFilter);
} else {
fileSet.add(item);
}
}
}
if (!fileSet.isEmpty())
dirMap.put(root, fileSet);
}
/**
*
* @function: TODO
* @author: guo
* @return
*/
private FilenameFilter getFilter() {
return new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
for (String extension : filterFileExtension) {
File currFile = new File(dir, filename);
if (currFile.isDirectory())
return true;
if (filename.toLowerCase(Locale.US).endsWith(
extension.toLowerCase(Locale.US)))
return true;
}
return false;
}
};
}
/**
*
* @function: TODO
* @author: guo
* @param dirMap
* @return
*/
private static LinkedList<File> dirMapToFile(Map<File, Set<File>> dirMap) {
LinkedList<File> fileList = new LinkedList<File>();
if (dirMap != null && dirMap.size() > 0) {
Set<Entry<File, Set<File>>> entries = dirMap.entrySet();
for (Entry<File, Set<File>> entry : entries) {
//fileList.add(entry.getKey());
for (File file : entry.getValue())
fileList.add(file);
}
}
return fileList;
}
}
这个类的作用就是扫描制定路径下的字体文件,为了配套使用我加了一个字体解析器,解析字体名称,如下:
package com.boyue.fontmanager.util;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
public class TTFParser {
public static int COPYRIGHT = 0;
public static int FAMILY_NAME = 1;
public static int FONT_SUBFAMILY_NAME = 2;
public static int UNIQUE_FONT_IDENTIFIER = 3;
public static int FULL_FONT_NAME = 4;
public static int VERSION = 5;
public static int POSTSCRIPT_NAME = 6;
public static int TRADEMARK = 7;
public static int MANUFACTURER = 8;
public static int DESIGNER = 9;
public static int DESCRIPTION = 10;
public static int URL_VENDOR = 11;
public static int URL_DESIGNER = 12;
public static int LICENSE_DESCRIPTION = 13;
public static int LICENSE_INFO_URL = 14;
private Map<Integer, String> fontProperties = new HashMap<Integer, String>();
/**
*
* 获取ttf font name
*
* @return
*/
public String getFontName() {
if (fontProperties.containsKey(FULL_FONT_NAME)) {
return fontProperties.get(FULL_FONT_NAME);
} else if (fontProperties.containsKey(FAMILY_NAME)) {
return fontProperties.get(FAMILY_NAME);
} else {
return null;
}
}
/**
*
* 获取ttf属性
*
* @param nameID
* 属性标记,见静态变量
*
* @return 属性值
*/
public String getFontPropertie(int nameID) {
if (fontProperties.containsKey(nameID)) {
return fontProperties.get(nameID);
} else {
return null;
}
}
/**
*
* 获取ttf属性集合
*
* @return 属性集合(MAP)
*/
public Map<Integer, String> getFontProperties() {
return fontProperties;
}
/**
*
* 执行解析
*
* @param fileName
* ttf文件名
*
* @throws IOException
*/
public void parse(String fileName) throws IOException {
fontProperties.clear();
RandomAccessFile f = null;
try {
f = new RandomAccessFile(fileName, "r");
parseInner(f);
} finally {
try {
f.close();
} catch (Exception e) {
// ignore;
}
}
}
private void parseInner(RandomAccessFile randomAccessFile)
throws IOException {
int majorVersion = randomAccessFile.readShort();
int minorVersion = randomAccessFile.readShort();
int numOfTables = randomAccessFile.readShort();
if (majorVersion != 1 || minorVersion != 0) {
return;
}
// jump to TableDirectory struct
randomAccessFile.seek(12);
boolean found = false;
byte[] buff = new byte[4];
TableDirectory tableDirectory = new TableDirectory();
for (int i = 0; i < numOfTables; i++) {
randomAccessFile.read(buff);
tableDirectory.name = new String(buff);
tableDirectory.checkSum = randomAccessFile.readInt();
tableDirectory.offset = randomAccessFile.readInt();
tableDirectory.length = randomAccessFile.readInt();
if ("name".equalsIgnoreCase(tableDirectory.name)) {
found = true;
break;
} else if (tableDirectory.name == null
|| tableDirectory.name.length() == 0) {
break;
}
}
// not found table of name
if (!found) {
return;
}
randomAccessFile.seek(tableDirectory.offset);
NameTableHeader nameTableHeader = new NameTableHeader();
nameTableHeader.fSelector = randomAccessFile.readShort();
nameTableHeader.nRCount = randomAccessFile.readShort();
nameTableHeader.storageOffset = randomAccessFile.readShort();
NameRecord nameRecord = new NameRecord();
for (int i = 0; i < nameTableHeader.nRCount; i++) {
nameRecord.platformID = randomAccessFile.readShort();
nameRecord.encodingID = randomAccessFile.readShort();
nameRecord.languageID = randomAccessFile.readShort();
nameRecord.nameID = randomAccessFile.readShort();
nameRecord.stringLength = randomAccessFile.readShort();
nameRecord.stringOffset = randomAccessFile.readShort();
long pos = randomAccessFile.getFilePointer();
byte[] bf = new byte[nameRecord.stringLength];
long vpos = tableDirectory.offset + nameRecord.stringOffset
+ nameTableHeader.storageOffset;
randomAccessFile.seek(vpos);
randomAccessFile.read(bf);
String temp = new String(bf, Charset.forName("utf-16"));
fontProperties.put(nameRecord.nameID, temp);
randomAccessFile.seek(pos);
}
}
@Override
public String toString() {
return fontProperties.toString();
}
private static class TableDirectory {
String name; // table name
int checkSum; // Check sum
int offset; // Offset from beginning of file
int length; // length of the table in bytes
}
private static class NameTableHeader {
int fSelector; // format selector. Always 0
int nRCount; // Name Records count
int storageOffset; // Offset for strings storage,
}
private static class NameRecord {
int platformID;
int encodingID;
int languageID;
int nameID;
int stringLength;
int stringOffset; // from start of storage area
}
}
基本上工具类准备好了,就要实现逻辑了,我直接上代码:
package com.boyue.fontmanager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.boyue.fontmanager.util.FileTool;
import com.boyue.fontmanager.util.SearchFile;
import com.boyue.fontmanager.util.TTFParser;
import android.app.IActivityManager;
import android.app.ActivityManagerNative;
import android.os.SystemProperties;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
public static final String SYSTEM_FONT_NAME = "default_font";
public static final String SYSTEM_FONT_PATH = "/system/fonts/DroidSansFallback.ttf";
public static final String DATA_FONT_DIR = "/data/fonts";
public static final String DATA_FONT_FILE = "/data/fonts/test.ttf";
public static final String SAVE_SYSTEM_FONT = "/data/fonts/DroidSansFallback.ttf";
private ListView mFontsListView;
private ArrayList<Font> mFonts;
private FontAdapter mAdapter;
private ProgressDialog mProgressDialog;
/**
* The position of current used font.
*/
private int mCurrentPosition;
/**
* The number of font in the system.
*/
private int mFontCount;
private Object mLock = new Object();
private ImageView mCheckBox;
private LinearLayout ll_back;
private AtomicInteger id = new AtomicInteger();
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
LinkedList<File> fonts = (LinkedList<File>) msg.obj;
for (File file : fonts) {
Font font = new Font();
font.setmFontName(file.getName());
font.setmFontPath(file.getAbsolutePath());
font.setmFontId(id.incrementAndGet() + "");
font.setChecked(false);
mFonts.add(font);
}
mAdapter.notifyDataSetChanged();
}
};
//mount -o remount rw /system
@Override
public void onCreate(Bundle icicle) {
// TODO Auto-generated method stub
Log.i("gcy1230", "FontManager onCreate() run");
super.onCreate(icicle);
setContentView(R.layout.font_setting_layout);
mFonts = new ArrayList<Font>();
loadDefaultFont();
FileTool.mkdir(DATA_FONT_DIR);
FileTool.chgPermission(DATA_FONT_DIR);
if(!new File(SAVE_SYSTEM_FONT).exists()){
FileTool.copyFileToDir(SYSTEM_FONT_PATH, DATA_FONT_DIR);
}
// 从根目录的开始扫描Font
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
File path = new File("/mnt/");
LinkedList<File> fonts = new SearchFile().scanFont(path);
handler.obtainMessage(1, fonts).sendToTarget();
}
}).start();
mFontsListView = (ListView) findViewById(R.id.font_types_lv);
mAdapter = new FontAdapter(this, mFonts);
mFontsListView.setAdapter(mAdapter);
mFontsListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// TODO Auto-generated method stub
if (mCurrentPosition != position) {
showSetFontDialog();
// 更新Adapter数据
mFonts.get(position).setChecked(true);
mFonts.get(mCurrentPosition).setChecked(false);
mCurrentPosition = position;
SystemProperties.set("persist.sys.fontset",
mFonts.get(position).getmFontName());
view.postDelayed(new Runnable() {
public void run() {
new SetFontTask().execute(mCurrentPosition);
}
}, 350);
} else {
}
}
});
}
private class SetFontTask extends AsyncTask<Integer, Void, Void> {
@Override
protected Void doInBackground(Integer... types) {
int position = types[0];
String srcFile = mFonts.get(position).getmFontPath();
FileTool.copyFileToFile(srcFile, DATA_FONT_FILE);
FileTool.chgPermission(DATA_FONT_FILE);
SystemProperties.set("ctl.start",
"change_font");
isFinishService();
IActivityManager am = ActivityManagerNative.getDefault();
try {
synchronized (mLock) {
if (mFonts == null) {
Log.e(TAG,
"doInBackground error occured, mThemeDatas becomes null.");
}
Configuration config = am.getConfiguration();
config.font = mFonts.get(position).getmFontName()
.toString();
Log.d(TAG,
"doInBackground() am.updateConfiguration() config.font = "
+ config.font);
am.updateConfiguration(config);
Log.i("gcy", "Main Activity updateConfiguration method run");
}
} catch (RemoteException e) {
Log.e(TAG, "Update configuration for font changed failed.");
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute() {
showSetFontDialog();
}
@Override
protected void onPostExecute(Void unused) {
finishSetFontDialog();
}
}
private boolean isFinishService() {
while (true) {
String mount_rt = SystemProperties.get("init.svc.change_font", "");
if (mount_rt != null && mount_rt.equals("stopped")) {
return true;
}
try {
Thread.sleep(1000);
} catch (Exception ex) {
Log.e(TAG, "Exception: " + ex.getMessage());
}
}
}
private void showSetFontDialog() {
if (mProgressDialog == null) {
mProgressDialog = ProgressDialog.show(this, null,
getString(R.string.loading), true, false);
} else {
}
}
private void finishSetFontDialog() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
}
private void loadDefaultFont() {
Font font = new Font();
font.setmFontName(SYSTEM_FONT_NAME);
font.setmFontPath(SAVE_SYSTEM_FONT);
font.setmFontId(id.incrementAndGet() + "");
font.setChecked(false);
mFonts.add(font);
}
@Override
public void onResume() {
// TODO Auto-generated method stub
super.onResume();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
finishSetFontDialog();
super.onDestroy();
}
class FontAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<Font> mDatas;
public FontAdapter(Context context, ArrayList<Font> datas) {
mContext = context;
mDatas = datas;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mDatas.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return mDatas.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = getLayoutInflater();
if (convertView == null) {
convertView = mInflater.inflate(R.layout.font_item, null);
holder = new ViewHolder();
holder.mText = (TextView) convertView
.findViewById(R.id.font_name);
holder.mCheck = (ImageView) convertView
.findViewById(R.id.font_check);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Font fontData = mDatas.get(position);
if (fontData.getmFontName().equals(SYSTEM_FONT_NAME)) {
holder.mText.setText(getResources().getString(
R.string.default_font));
} else {
String fontName = getFileNameTitle(fontData.getmFontPath());
if (null == fontName || fontName.isEmpty())
holder.mText.setText(getFileNameTitleWithParser(fontData
.getmFontName()));
else
holder.mText.setText(fontName);
}
String fontName = SystemProperties.get("persist.sys.fontset",
SYSTEM_FONT_NAME);
if (fontName.trim().equals(fontData.getmFontName().trim())) {
Log.i("fontManager", "gcy position = " + position);
Log.i("fontManager", "gcy mCurrentPosition = " + position);
mFonts.get(position).setChecked(true);
holder.mCheck.setVisibility(View.VISIBLE);
mCurrentPosition = position;
} else {
mFonts.get(position).setChecked(false);
holder.mCheck.setVisibility(View.GONE);
}
return convertView;
}
public String getFileNameTitleWithParser(String fileName) {
int index = fileName.lastIndexOf(".");
if (index > 0) {
return fileName.substring(0, index);
}
return "";
}
public String getFileNameTitle(String filePath) {
TTFParser parser = new TTFParser();
try {
parser.parse(filePath);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return parser.getFontName();
}
}
static class ViewHolder {
TextView mText;
ImageView mCheck;
}
}
解释主要流程,及几个重要方法的作用。
1.加载字体数据到listview :通过新线程加载字体数据,保存到字体数据集合。
2点击listview某个Item时的处理:
主要做的事情:
1.数据状态的跟新。
//更新Adapter数据
mFonts.get(position).setChecked(true);
mFonts.get(mCurrentPosition).setChecked(false);
mCurrentPosition = position;
2.新开异步任务请求init 进程执行数据更新脚本。
至此,大致流程以说明。