如何制作可以更改 Android 用户界面字体的应用程序?

How to make app that can change Android user interface font?

我想知道是否有人知道如何创建一个可以更改三星手机界面字体样式的应用程序。我有我最喜欢的 TrueType 格式字体。

Galaxy Apps store 上有很多字体样式,但它们是付费的,不是我想要的。您可以看到 this 应用程序作为示例安装后用户可以转到设置 > 设备 > 字体样式 > 从列表中选择字体并更改字体样式。

我试过反编译它,但没有得到太多。或者换句话说,Flip Font app.

反编译源码

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="2" android:versionName="1.1" package="com.monotype.android.font.presentltroman" platformBuildVersionCode="23" platformBuildVersionName="6.0-2438415">
    <uses-sdk android:minSdkVersion="7" />
    <application android:label="@string/app_name" android:icon="@drawable/icon">
        <provider android:name=".FontContentProvider" android:authorities="com.example.myfont" />
        <support-screens android:largeScreens="true" android:xlargeScreens="true" />
    </application>
</manifest>

只有一个activity

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.FileNotFoundException;

public class FontContentProvider extends ContentProvider {
    private static final UriMatcher uriMatcher = new UriMatcher(-1);

    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        return null;
    }

    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
        String file_name = uri.getPath();
        if (file_name == null) {
            throw new FileNotFoundException();
        }
        if (file_name.startsWith("/")) {
            file_name = file_name.substring(1);
        }
        AssetFileDescriptor ad = null;
        try {
            ad = getContext().getAssets().openFd(file_name);
        } catch (Exception e) {
            Log.v("CPFontTest", "cp - openAssetFile EXCEPTION");
        }
        return ad;
    }

    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    public String getType(Uri uri) {
        AssetManager am = getContext().getAssets();
        StringBuilder xmlfileStringBuilder = new StringBuilder();
        try {
            for (String s : am.list("xml")) {
                xmlfileStringBuilder.append(s + "\n");
            }
            return xmlfileStringBuilder.toString();
        } catch (Exception e) {
            return null;
        }
    }

    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    public boolean onCreate() {
        return true;
    }

    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }

    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    static {
        uriMatcher.addURI(".fontcontentprovider", "fonts", 1);
    }
}

我尝试将它放入我的应用程序中,但它不起作用。

我猜你不能这样做,除非你需要 root 你的 phone,因为三星不允许你从它需要 root permission 的应用程序中更改字体,但我不确定!

While stock Android lacks the ability to customize your system font, plenty of manufacturers have adapted their software to support this highly requested feature, allowing you to easily change fonts for Android

Source

这意味着没有系统 API 您可以通过插件使字体在所有 Android 设备上工作。但是,个别制造商确实提供自定义字体实现。

我为您快速搜索了一下,但似乎这些制造商不提供 official/generalised APIs(或含糊不清的 Samsung)来更改其字体,相反,您必须单独查看每个制造商 theme/font 实施(逆向工程?),并了解如何设置系统范围的字体以及如何插入该系统。

如果您正在寻找 root 应用程序如何执行此操作的示例,有许多操纵字体的开源应用程序,like this

I/You 需要使用 Monotype 的密钥库对 APK 进行签名。 I/You 无权访问此内容。因此,您的 APK 将无法在三星设备上运行。 以下方法是从三星设备上的设置应用程序反编译的,用于检查 FlipFont APK 是否使用正确的密钥签名:

protected boolean checkFont(String apkname) {
  if (DEBUG) {
    Log.secD("FlipFont", "checkFont - checking apkname" + apkname);
  }
  if ("com.monotype.android.font.foundation".equals(apkname)) {
    return false;
  }
  PackageManager pm = this.mContext.getPackageManager();
  for (int i = 0; i < apkNameList.length; i++) {
    if (apkname != null) {
      if (apkname.equals(apkNameList[i])) {
        this.isCheckPlatformSignatures = pm.checkSignatures("android", apkNameList[i]) == 0;
        this.isCheckReleaseSignatures = Utils.isSignatureMatch(this.mContext, apkNameList[i]);
        Log.i("FontPreviewTablet", "apkname : " + apkname + ", isCheckPlatformSignatures : " + this.isCheckPlatformSignatures + ", isCheckReleaseSignatures : " + this.isCheckReleaseSignatures);
        if (!(this.isCheckPlatformSignatures || this.isCheckReleaseSignatures)) {
          if (apkname.equals("")) {
          }
        }
        return false;
      }
      continue;
    }
  }
  if (DEBUG) {
    Log.secD("FlipFont", "checkFont - check if valid certificate");
  }
  PackageInfo packageInfo = null;
  try {
    packageInfo = this.mFontListAdapter.mPackageManager.getPackageInfo(apkname, 64);
  } catch (Exception e) {
  }
  if (packageInfo != null) {
    Signature[] signatures = packageInfo.signatures;
    byte[] cert = signatures[0].toByteArray();
    try {
      MessageDigest md = MessageDigest.getInstance("SHA");
      md.update(signatures[0].toByteArray());
      if ("T84drf8v3ZMOLvt2SFG/K7ODXgI=".equals(Base64.encodeToString(md.digest(), 0).trim())) {
        if (DEBUG) {
          Log.v("FlipFont", "**Signature is correct**");
        }
        return false;
      }
      if (DEBUG) {
        Log.v("FlipFont", "**Signature is incorrect**");
      }
      return true;
    } catch (Exception e2) {
      e2.printStackTrace();
      InputStream input = new ByteArrayInputStream(cert);
      CertificateFactory cf = null;
      try {
        cf = CertificateFactory.getInstance("X509");
      } catch (CertificateException e3) {
        e3.printStackTrace();
      }
      X509Certificate c = null;
      try {
        c = (X509Certificate) cf.generateCertificate(input);
      } catch (CertificateException e32) {
        e32.printStackTrace();
      }
      if (DEBUG) {
        Log.secD("Example", "APK name: " + apkname);
        if (c != null) {
          Log.secD("Example", "Certificate for: " + c.getSubjectDN());
          Log.secD("Example", "Certificate issued by: " + c.getIssuerDN());
          Log.secD("Example", "The certificate is valid from " + c.getNotBefore() + " to " + c.getNotAfter());
          Log.secD("Example", "Certificate SN# " + c.getSerialNumber());
          Log.secD("Example", "Generated with " + c.getSigAlgName());
        }
      }
      String certIssuedByString = "CN=Ed Platz, OU=Display Imaging, O=Monotype Imanging Inc., L=Woburn, ST=MA, C=US";
      if (c != null && certIssuedByString.equals(c.getIssuerDN().toString())) {
        if (DEBUG) {
          Log.secD("FlipFont", "**Certificate data is correct**");
        }
        return false;
      }
    }
  }
  return true;
}

如果 I/you 查看上述方法,您会发现如果 APK 的包名称为 "com.monotype.android.font.foundation",则不会检查 APK 签名。