update: 完善 AndroidManifest 文档管理和导入功能

deeplink
胡宇飞 2024-03-31 11:31:50 +08:00
parent 7bbbf34b60
commit 17ea49120a
7 changed files with 344 additions and 177 deletions

View File

@ -12,7 +12,7 @@ namespace Guru.Editor
/// <summary> /// <summary>
/// Android 配置修改器 /// Android 配置修改器
/// </summary> /// </summary>
public class MainManifestDoc public class AndroidManifestDoc
{ {
private const string TargetPath = "Plugins/Android/AndroidManifest.xml"; private const string TargetPath = "Plugins/Android/AndroidManifest.xml";
private const string XmlnsAndroid = "xmlns:android"; private const string XmlnsAndroid = "xmlns:android";
@ -20,7 +20,9 @@ namespace Guru.Editor
private const string XmlnsTools= "xmlns:tools"; private const string XmlnsTools= "xmlns:tools";
private const string NamespaceTools = "http://schemas.android.com/tools"; private const string NamespaceTools = "http://schemas.android.com/tools";
private const string UserPermission = "uses-permission";
private const string MetaData = "meta-data";
private const string KName = "name";
private XmlDocument _doc; private XmlDocument _doc;
public XmlDocument Doc => _doc; public XmlDocument Doc => _doc;
@ -30,8 +32,6 @@ namespace Guru.Editor
private XmlElement _manifestNode; private XmlElement _manifestNode;
private XmlElement _applicationNode; private XmlElement _applicationNode;
private XmlNodeList _metadataList;
private XmlNodeList _permissionList;
#region Initiallize #region Initiallize
@ -41,7 +41,7 @@ namespace Guru.Editor
/// </summary> /// </summary>
/// <param name="docPath"></param> /// <param name="docPath"></param>
/// <returns></returns> /// <returns></returns>
public static MainManifestDoc Load(string docPath = "") public static AndroidManifestDoc Load(string docPath = "")
{ {
if (string.IsNullOrEmpty(docPath)) if (string.IsNullOrEmpty(docPath))
{ {
@ -54,14 +54,14 @@ namespace Guru.Editor
return null; return null;
} }
var mod = new MainManifestDoc(); var mod = new AndroidManifestDoc();
mod.ReadFromPath(docPath); mod.ReadFromPath(docPath);
return mod; return mod;
} }
public static MainManifestDoc Read(string xmlStr, string docPath = "") public static AndroidManifestDoc Read(string xmlStr, string docPath = "")
{ {
var mod = new MainManifestDoc(); var mod = new AndroidManifestDoc();
mod.ReadFromXml(xmlStr, docPath); mod.ReadFromXml(xmlStr, docPath);
return mod; return mod;
} }
@ -103,10 +103,6 @@ namespace Guru.Editor
// --- Root Nodes --- // --- Root Nodes ---
_manifestNode = _doc.SelectSingleNode("manifest") as XmlElement; _manifestNode = _doc.SelectSingleNode("manifest") as XmlElement;
_applicationNode = _doc.SelectSingleNode("manifest/application") as XmlElement; _applicationNode = _doc.SelectSingleNode("manifest/application") as XmlElement;
// --- Metadatas ---
_metadataList = _applicationNode.SelectNodes("meta-data");
// --- Permissions ---
_permissionList = _applicationNode.SelectNodes("uses-permission");
AddXmlnsAndroid(); AddXmlnsAndroid();
AddXmlnsTools(); AddXmlnsTools();
@ -158,7 +154,6 @@ namespace Guru.Editor
#endregion #endregion
#region API #region API
public bool AddXmlnsAndroid() public bool AddXmlnsAndroid()
@ -175,14 +170,14 @@ namespace Guru.Editor
/// Add Replace Item /// Add Replace Item
/// </summary> /// </summary>
/// <param name="item"></param> /// <param name="item"></param>
public void AddReplaceItem(string item) public void AddApplicationReplaceItem(string item)
{ {
if (_manifestNode != null) if (_applicationNode != null)
{ {
List<string> items = new List<string>(5); List<string> items = new List<string>(5);
if (_manifestNode.HasAttribute("replace", NamespaceTools)) if (_applicationNode.HasAttribute("replace", NamespaceTools))
{ {
var arr = _manifestNode.GetAttribute("replace",NamespaceTools).Split(','); var arr = _applicationNode.GetAttribute("replace",NamespaceTools).Split(',');
if(arr != null && arr.Length > 0) if(arr != null && arr.Length > 0)
{ {
items.AddRange(arr); items.AddRange(arr);
@ -191,10 +186,17 @@ namespace Guru.Editor
if (!items.Contains(item)) items.Add(item); if (!items.Contains(item)) items.Add(item);
_manifestNode.SetAttribute("replace", NamespaceTools, string.Join(",", items)); _applicationNode.SetAttribute("replace", NamespaceTools, string.Join(",", items));
} }
} }
public void SetApplicationAttribute(string key, string value)
{
if (_applicationNode != null)
{
_applicationNode.SetAttribute(key, NamespaceAndroid, value);
}
}
/// <summary> /// <summary>
/// Set metadata /// Set metadata
@ -203,46 +205,121 @@ namespace Guru.Editor
/// <param name="value"></param> /// <param name="value"></param>
/// <param name="valueName"></param> /// <param name="valueName"></param>
/// <param name="keyName"></param> /// <param name="keyName"></param>
public void SetMetadata(string key, string value, string valueName = "value", string keyName = "name") public void SetMetadata(string key, string value, string valueName = "value", string keyName = KName)
{ {
_metadataList = _applicationNode?.SelectNodes("meta-data") ?? null; if (_doc == null || !_isReady) return;
XmlElement item = null; XmlElement node = null;
if (_metadataList != null) if (!TryGetMetadata(key, out node, keyName))
{ {
foreach (XmlElement e in _metadataList) node = _doc.CreateElement(MetaData);
{ _applicationNode?.AppendChild(node);
// var nm = e.GetAttribute("name", NamespaceAndroid);
if (e.GetAttribute(keyName, NamespaceAndroid) is string k && k == key)
{
item = e;
break;
}
}
} }
if (item == null) node.SetAttribute(keyName, NamespaceAndroid, key);
{ node.SetAttribute(valueName, NamespaceAndroid, value);
item = _doc.CreateElement("meta-data");
_applicationNode?.AppendChild(item);
}
item.SetAttribute(keyName, NamespaceAndroid, key);
item.SetAttribute(valueName, NamespaceAndroid, value);
} }
/// <summary>
public void SetPermission(string key, string value, string valueName = "value", string keyName = "name") /// 添加权限
/// </summary>
/// <param name="key"></param>
/// <param name="keyName"></param>
public void AddPermission(string key, string keyName = KName)
{ {
_metadataList = _applicationNode?.SelectNodes("meta-data") ?? null; if (_doc == null || !_isReady) return;
XmlElement node = null;
if(!TryGetPermission(key, out node, keyName))
{
node = _doc.CreateElement(UserPermission);
_manifestNode?.AppendChild(node);
}
node.SetAttribute(keyName, NamespaceAndroid, key);
}
/// <summary>
/// 删除 Permission
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="keyName"></param>
/// <param name="valueName"></param>
public void RemovePermission(string key, string value = "remove", string keyName = "name", string valueName = "node")
{
if (_doc == null || !_isReady) return;
XmlElement node = null;
if(!TryGetPermission(key, out node, keyName))
{
node = _doc.CreateElement(UserPermission);
_manifestNode?.AppendChild(node);
}
node.SetAttribute(keyName, NamespaceAndroid, key);
node.SetAttribute(valueName, NamespaceTools, value);
}
public bool SetPackageName(string packageName)
{
if (_manifestNode != null)
{
_manifestNode.Attributes["package"].Value = packageName;
return true;
}
return false;
} }
#endregion #endregion
#region Data Opration
public bool TryGetMetadata(string name, out XmlElement node, string keyName = KName)
{
node = null;
if(_applicationNode != null)
{
var list = _applicationNode.SelectNodes(MetaData);
if (list != null)
{
foreach (XmlElement e in list)
{
if (e.GetAttribute(keyName, NamespaceAndroid) == name)
{
node = e;
return true;
}
}
}
}
return false;
}
public bool TryGetPermission(string name, out XmlElement node, string keyName = KName)
{
node = null;
if(_manifestNode != null)
{
var list = _manifestNode.SelectNodes(UserPermission);
if (list != null)
{
foreach (XmlElement e in list)
{
if (e.GetAttribute(keyName, NamespaceAndroid) == name)
{
node = e;
return true;
}
}
}
}
return false;
}
#endregion
#region Output #region Output
@ -256,13 +333,5 @@ namespace Guru.Editor
#endregion #endregion
} }
} }

View File

@ -15,10 +15,12 @@ namespace Guru.Editor
private const string TargetPath = "Plugins/Android/AndroidManifest.xml"; private const string TargetPath = "Plugins/Android/AndroidManifest.xml";
private const string ValOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"; private const string ValOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION";
private const string ValOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"; private const string ValOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING";
private const string NamespaceAndroid = "http://schemas.android.com/apk/res/android";
private const string NamespaceTools = "http://schemas.android.com/tools";
private const string PermissionReadPostNotifications = "android.permission.POST_NOTIFICATIONS";
private const string PermissionReadPhoneState = "android.permission.READ_PHONE_STATE"; private const string PermissionReadPhoneState = "android.permission.READ_PHONE_STATE";
private const string NetworkSecurityConfig = "networkSecurityConfig";
private const string NetworkSecurityConfigValue = "@xml/network_security_config";
private static string TargetFullPath = Path.Combine(Application.dataPath, TargetPath); private static string TargetFullPath = Path.Combine(Application.dataPath, TargetPath);
@ -31,121 +33,36 @@ namespace Guru.Editor
CopyManifest(); CopyManifest();
} }
var doc = new XmlDocument(); FixAndroidManifest();
doc.Load(TargetFullPath);
SetApplicationMod(doc);
SetPermissionMod(doc);
doc.Save(TargetFullPath);
} }
/// <summary> /// <summary>
/// Fix all Elements in <Applicaiton> /// Fix Android Manifest
/// </summary> /// </summary>
/// <param name="doc"></param> private static void FixAndroidManifest()
private static void SetApplicationMod(XmlDocument doc)
{ {
string rootName = "manifest/application"; var doc = AndroidManifestDoc.Load(TargetFullPath);
var rootNode = doc.SelectSingleNode(rootName);
int item1 = 0;
int item2 = 0;
if (rootNode == null) // --- network_security_config ---
{ doc.SetApplicationAttribute(NetworkSecurityConfig, NetworkSecurityConfigValue);
Debug.LogError($"Can't find root with name {rootName} ..."); doc.AddApplicationReplaceItem($"android:{NetworkSecurityConfig}");
return; // ---- Metadata ---
doc.SetMetadata(ValOptimizeInitialization, "true");
doc.SetMetadata(ValOptimizeAdLoading, "true");
// ---- Permission ---
doc.AddPermission(PermissionReadPostNotifications);
doc.RemovePermission(PermissionReadPhoneState);
// --- Bundle Id ---
doc.SetPackageName(PlayerSettings.applicationIdentifier);
doc.Save();
} }
XmlNodeList metadatas = rootNode.SelectNodes("meta-data");
if (metadatas != null && metadatas.Count > 0)
{
bool isDirty = false;
foreach (XmlElement e in metadatas)
{
if (e != null)
{
if (e.HasAttribute("name", NamespaceAndroid))
{
if (e.Attributes["android:name"].Value == ValOptimizeInitialization) item1 = 1;
if (e.Attributes["android:name"].Value == ValOptimizeAdLoading) item2 = 1;
}
}
}
}
if (item1 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",NamespaceAndroid, ValOptimizeInitialization);
e.SetAttribute("value",NamespaceAndroid, "true");
rootNode.AppendChild(e);
}
if (item2 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",NamespaceAndroid,ValOptimizeAdLoading);
e.SetAttribute("value",NamespaceAndroid, "true");
rootNode.AppendChild(e);
}
var rootE = doc.SelectSingleNode("manifest") as XmlElement;
if (rootE != null)
{
rootE.Attributes["package"].Value = PlayerSettings.applicationIdentifier; // 写入包名
}
}
/// <summary> /// <summary>
/// Fix all permissions /// 拷贝 AndroidManifest
/// </summary> /// </summary>
/// <param name="doc"></param>
private static void SetPermissionMod(XmlDocument doc)
{
string attName = "uses-permission";
string rootName = "manifest";
bool isBuild = false;
var rootNode = doc.SelectSingleNode(rootName);
XmlElement item1 = null;
if (rootNode == null)
{
Debug.LogError($"Can't find root with name {rootName} ...");
return;
}
XmlNodeList permissions = rootNode.SelectNodes(attName);
if (permissions != null && permissions.Count > 0)
{
foreach (XmlElement e in permissions)
{
if (e != null)
{
if (e.HasAttribute("android:name"))
{
if (e.Attributes["android:name"].Value == PermissionReadPhoneState) item1 = e;
}
}
}
}
isBuild = false;
if (item1 == null)
{
isBuild = true;
item1 = doc.CreateElement(attName);
}
item1.SetAttribute("name",NamespaceAndroid, PermissionReadPhoneState);
item1.SetAttribute("node",NamespaceTools, "remove");
if (isBuild) rootNode.AppendChild(item1);
}
private static void CopyManifest() private static void CopyManifest()
{ {
if (File.Exists(TargetFullPath)) return; if (File.Exists(TargetFullPath)) return;
@ -161,9 +78,6 @@ namespace Guru.Editor
} }
} }
#region Testing #region Testing
[Test] [Test]

View File

@ -0,0 +1,178 @@
using System.Collections;
using Unity.EditorCoroutines.Editor;
namespace Guru.Editor
{
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using System;
using System.IO;
using System.Xml;
public static class AndroidManifestMod
{
private const string TargetPath = "Plugins/Android/AndroidManifest.xml";
private const string ValOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION";
private const string ValOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING";
private const string NamespaceAndroid = "http://schemas.android.com/apk/res/android";
private const string NamespaceTools = "http://schemas.android.com/tools";
private const string PermissionReadPhoneState = "android.permission.READ_PHONE_STATE";
private static string TargetFullPath = Path.Combine(Application.dataPath, TargetPath);
public static bool IsManifestExist() => File.Exists(TargetFullPath);
public static void Apply()
{
if (!IsManifestExist())
{
CopyManifest();
}
var doc = new XmlDocument();
doc.Load(TargetFullPath);
SetApplicationMod(doc);
SetPermissionMod(doc);
doc.Save(TargetFullPath);
}
/// <summary>
/// Fix all Elements in <Applicaiton>
/// </summary>
/// <param name="doc"></param>
private static void SetApplicationMod(XmlDocument doc)
{
string rootName = "manifest/application";
var rootNode = doc.SelectSingleNode(rootName);
int item1 = 0;
int item2 = 0;
if (rootNode == null)
{
Debug.LogError($"Can't find root with name {rootName} ...");
return;
}
XmlNodeList metadatas = rootNode.SelectNodes("meta-data");
if (metadatas != null && metadatas.Count > 0)
{
bool isDirty = false;
foreach (XmlElement e in metadatas)
{
if (e != null)
{
if (e.HasAttribute("name", NamespaceAndroid))
{
if (e.Attributes["android:name"].Value == ValOptimizeInitialization) item1 = 1;
if (e.Attributes["android:name"].Value == ValOptimizeAdLoading) item2 = 1;
}
}
}
}
if (item1 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",NamespaceAndroid, ValOptimizeInitialization);
e.SetAttribute("value",NamespaceAndroid, "true");
rootNode.AppendChild(e);
}
if (item2 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",NamespaceAndroid,ValOptimizeAdLoading);
e.SetAttribute("value",NamespaceAndroid, "true");
rootNode.AppendChild(e);
}
var rootE = doc.SelectSingleNode("manifest") as XmlElement;
if (rootE != null)
{
rootE.Attributes["package"].Value = PlayerSettings.applicationIdentifier; // 写入包名
}
}
/// <summary>
/// Fix all permissions
/// </summary>
/// <param name="doc"></param>
private static void SetPermissionMod(XmlDocument doc)
{
string attName = "uses-permission";
string rootName = "manifest";
bool isBuild = false;
var rootNode = doc.SelectSingleNode(rootName);
XmlElement item1 = null;
if (rootNode == null)
{
Debug.LogError($"Can't find root with name {rootName} ...");
return;
}
XmlNodeList permissions = rootNode.SelectNodes(attName);
if (permissions != null && permissions.Count > 0)
{
foreach (XmlElement e in permissions)
{
if (e != null)
{
if (e.HasAttribute("android:name"))
{
if (e.Attributes["android:name"].Value == PermissionReadPhoneState) item1 = e;
}
}
}
}
isBuild = false;
if (item1 == null)
{
isBuild = true;
item1 = doc.CreateElement(attName);
}
item1.SetAttribute("name",NamespaceAndroid, PermissionReadPhoneState);
item1.SetAttribute("node",NamespaceTools, "remove");
if (isBuild) rootNode.AppendChild(item1);
}
private static void CopyManifest()
{
if (File.Exists(TargetFullPath)) return;
var path = GuruEditorHelper.GetFilePath($"{nameof(AndroidManifestMod)} t:Script");
if (!string.IsNullOrEmpty(path))
{
var from = Path.GetFullPath($"{path}/../../Files/AndroidManifest.txt");
if (File.Exists(from))
{
File.Copy(from, TargetFullPath);
}
}
}
#region Testing
[Test]
public static void Test_Injection()
{
Apply();
}
#endregion
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f4547abfcddf84bc6b61e884ebfb30e2
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -137,7 +137,7 @@ namespace Guru.Editor
File.WriteAllText(path, content); File.WriteAllText(path, content);
// ----- Inject AndroidManifest.xml ------ // ----- Inject AndroidManifest.xml ------
var doc = MainManifestDoc.Load(); var doc = AndroidManifestDoc.Load();
if (doc != null) if (doc != null)
{ {
doc.SetMetadata("com.google.firebase.messaging.default_notification_icon", "@drawable/ic_notification", valueName:"resource"); doc.SetMetadata("com.google.firebase.messaging.default_notification_icon", "@drawable/ic_notification", valueName:"resource");

View File

@ -1006,7 +1006,6 @@ namespace Guru.Editor
#endregion #endregion
#region Push Icon Maker #region Push Icon Maker
private bool _showSegmentPush = false; private bool _showSegmentPush = false;