ApkDecoder.cs 11.6 KB
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using static System.Net.Mime.MediaTypeNames;

namespace ApkInfo
{
    public class ApkDecoder
    {
        public event Action<ApkDecoder> InfoParsedEvent;
        //public event MethodInvoker AaptNotFoundEvent;
        public static Dictionary<int, string> SdkMap = new Dictionary<int, string> {
            {1, "Android 1.0 / BASE"},
            {2, "Android 1.1 / BASE_1_1"},
            {3, "Android 1.5 / CUPCAKE"},
            {4, "Android 1.6 / DONUT"},
            {5, "Android 2.0 / ECLAIR"},
            {6, "Android 2.0.1 / ECLAIR_0_1"},
            {7, "Android 2.1.x / ECLAIR_MR1"},
            {8, "Android 2.2.x / FROYO"},
            {9, "Android 2.3, 2.3.1, 2.3.2 / GINGERBREAD"},
            {10, "Android 2.3.3, 2.3.4 / GINGERBREAD_MR1"},
            {11, "Android 3.0.x / HONEYCOMB"},
            {12, "Android 3.1.x / HONEYCOMB_MR1"},
            {13, "Android 3.2 / HONEYCOMB_MR2"},
            {14, "Android 4.0, 4.0.1, 4.0.2 / ICE_CREAM_SANDWICH"},
            {15, "Android 4.0.3, 4.0.4 / ICE_CREAM_SANDWICH_MR1"},
            {16, "Android 4.1, 4.1.1 / JELLY_BEAN"},
            {17, "Android 4.2, 4.2.2 / JELLY_BEAN_MR1"},
            {18, "Android 4.3 / JELLY_BEAN_MR2"},
            {19, "Android 4.4 / KITKAT"}
        };

        private string appPath;
        private string apkPath;
        private List<string> infos = new List<string>();

        public ApkDecoder(string apkPath, string appPath)
        {
            this.appPath = appPath; //Path.GetDirectoryName(appPath);
            this.apkPath = apkPath;
            new WaitCallback(Decoder).Invoke(null);
        }

        public string ApkPath
        {
            get { return this.apkPath; }
        }

        public string ApkSize
        {
            get { return GetApkSize(this.apkPath); }
        }

        public string AppName { get; private set; }
        public string AppVersion { get; private set; }
        public string AppVersionCode { get; private set; }
        public string PkgName { get; private set; }
        public string IconPath { get; private set; }
        public System.Drawing.Image AppIcon;
        public string MinSdk { get; private set; }
        public string MinVersion { get; private set; }
        public string ScreenSupport { get; private set; }
        public string ScreenSolutions { get; private set; }
        public string Permissions { get; private set; }
        public string Features { get; private set; }

        private string GetApkSize(string apkPath)
        {
            string apkSize = "0 M";
            if (!File.Exists(apkPath))
                return apkSize;

            FileInfo fi = new FileInfo(apkPath);
            if (fi.Length >= 1024 * 1024)
            {
                apkSize = string.Format("{0:N2} M", fi.Length / (1024 * 1024f));
            }
            else
            {
                apkSize = string.Format("{0:N2} K", fi.Length / 1024f);
            }
            return apkSize;
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern int GetShortPathName(
           [MarshalAs(UnmanagedType.LPTStr)] string path,
           [MarshalAs(UnmanagedType.LPTStr)] StringBuilder short_path,
           int short_len);

        private void Decoder(object state)
        {
            if (!File.Exists(this.apkPath))
                return;
            string aaptPath = Path.Combine(this.appPath, @"tools\aapt.exe");
            if (!File.Exists(aaptPath))
                aaptPath = Path.Combine(this.appPath, @"aapt.exe");
            //if (!File.Exists(aaptPath))
            //{
            //    var handler = AaptNotFoundEvent;
            //    if (handler != null)
            //        handler();
            //    return;
            //}

            StringBuilder sb = new StringBuilder(255);
            int result = GetShortPathName(aaptPath, sb, 255);
            if (result != 0)
                aaptPath = sb.ToString();

            var startInfo = new ProcessStartInfo("cmd.exe");
            try
            {
                string dumpFile = Path.GetTempFileName();
                //如此费事做中转,只为处理中文乱码
                string args = string.Format("/k {0} dump badging \"{1}\" > \"{2}\" &exit", aaptPath, this.apkPath, dumpFile);
                startInfo.Arguments = args;
                startInfo.WindowStyle = ProcessWindowStyle.Hidden;
                this.infos.Clear();
                using (var process = Process.Start(startInfo))
                {
                }
                Thread.Sleep(2000);
                if (File.Exists(dumpFile))
                {
                    //解析
                    using (var sr = new StreamReader(dumpFile, Encoding.UTF8))
                    {
                        string line;
                        while ((line = sr.ReadLine()) != null)
                        {
                            this.infos.Add(line);
                        }
                        ParseInfo();
                    }

                    File.Delete(dumpFile);
                }
            }
            catch
            {
                //出了异常,换回命令行解析方式
                aaptPath = Path.Combine(this.appPath, @"tools\aapt.exe");
                if (!File.Exists(aaptPath))
                    aaptPath = Path.Combine(this.appPath, @"aapt.exe");
                startInfo = new ProcessStartInfo(aaptPath);
                string args = string.Format("dump badging \"{0}\"", this.apkPath);
                startInfo.Arguments = args;
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardOutput = true;
                startInfo.CreateNoWindow = true;
                using (var process = Process.Start(startInfo))
                {
                    var sr = process.StandardOutput;
                    while (!sr.EndOfStream)
                    {
                        infos.Add(sr.ReadLine());
                    }
                    process.WaitForExit();
                    //解析
                    ParseInfo(sr.CurrentEncoding);
                }
            }
        }

        private void ParseInfo(Encoding currentEncoding = null)
        {
            if (this.infos.Count == 0)
            {
                InvokeParsedHandler();
                return;
            }

            DoParseInfo();
            InvokeParsedHandler();
        }

        private void InvokeParsedHandler()
        {
            var hander = InfoParsedEvent;
            if (hander != null)
                hander(this);
        }

        private void DoParseInfo(Encoding currentEncoding = null)
        {
            //解析每个字串
            foreach (var info in this.infos)
            {
                if (string.IsNullOrEmpty(info))
                    continue;

                //application: label='MobileGo™' icon='r/l/icon.png'
                if (info.IndexOf("application:") == 0)
                {
                    this.AppName = GetKeyValue(info, "label=");
                    if (currentEncoding != null)
                        this.AppName = Encoding.UTF8.GetString(currentEncoding.GetBytes(this.AppName));
                    this.IconPath = GetKeyValue(info, "icon=");
                    GetAppIcon(this.IconPath);
                }

                //package: name = 'com.wondershare.mobilego' versionCode = '4773' versionName = '7.5.2.4773'
                if (info.IndexOf("package:") == 0)
                {
                    this.PkgName = GetKeyValue(info, "name=");
                    this.AppVersion = GetKeyValue(info, "versionName=");
                    this.AppVersionCode = GetKeyValue(info, "versionCode=");
                }

                //sdkVersion:'8'
                if (info.IndexOf("sdkVersion:") == 0)
                {
                    this.MinSdk = GetKeyValue(info, "sdkVersion:");
                    this.MinVersion = string.Empty;
                    if (!string.IsNullOrEmpty(this.MinSdk))
                    {
                        int minSdk = 1;
                        if (int.TryParse(this.MinSdk, out minSdk) && minSdk >= 1 && minSdk <= 19)
                        {
                            this.MinVersion = SdkMap[minSdk];
                        }
                    }
                }

                //supports-screens: 'small' 'normal' 'large' 'xlarge'
                if (info.IndexOf("supports-screens:") == 0)
                {
                    this.ScreenSupport = info.Replace("supports-screens:", "").TrimStart().Replace("' '", ", ").Replace("'", "");
                }

                //densities: '120' '160' '213' '240' '320' '480' '640'
                if (info.IndexOf("densities:") == 0)
                {
                    this.ScreenSolutions = info.Replace("densities:", "").TrimStart().Replace("' '", ", ").Replace("'", "");
                }

                //uses-permission:'android.permission.READ_CONTACTS'
                //uses-permission:'android.permission.WRITE_CONTACTS'
                //uses-permission:'android.permission.READ_SMS'
                if (info.IndexOf("uses-permission:") == 0)
                {
                    string permission = info.Substring(info.LastIndexOf('.') + 1).Replace("'", "");
                    this.Permissions += permission + "\r\n";
                }

                //uses-feature:'android.hardware.touchscreen'
                if (info.IndexOf("uses-feature:") == 0)
                {
                    string feature = info.Substring(info.LastIndexOf('.') + 1).Replace("'", "");
                    this.Features += feature + "\r\n";
                }
            }
            if (!string.IsNullOrEmpty(this.Permissions))
            {
                this.Permissions = this.Permissions.Trim();
            }
            if (!string.IsNullOrEmpty(this.Features))
            {
                this.Features = this.Features.Trim();
            }
        }

        private string GetKeyValue(string info, string key)
        {
            if (info.IndexOf(key) != -1)
            {
                int start = info.IndexOf(key) + @key.Length + 1;
                return info.Substring(start, info.IndexOf("'", start) - start);
            }
            return string.Empty;
        }

        private void GetAppIcon(string iconPath)
        {
            if (string.IsNullOrEmpty(iconPath))
                return;
            string unzipPath = Path.Combine(appPath, @"tools\unzip.exe");
            if (!File.Exists(unzipPath))
                unzipPath = Path.Combine(appPath, @"unzip.exe");
            if (!File.Exists(unzipPath))
                return;

            string destPath = Path.Combine(Path.GetTempPath(), Path.GetFileName(iconPath));
            if (File.Exists(destPath))
            {
                File.Delete(destPath);
            }
            var startInfo = new ProcessStartInfo(unzipPath);
            string args = string.Format("-j \"{0}\" \"{1}\" -d \"{2}\"", this.apkPath, iconPath, Path.GetDirectoryName(Path.GetTempPath()));
            startInfo.Arguments = args;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
            using (var process = Process.Start(startInfo))
            {
                process.WaitForExit(2000);
            }

            if (File.Exists(destPath))
            {
                using (var fs = new FileStream(destPath, FileMode.Open, FileAccess.Read))
                {
                    this.AppIcon = System.Drawing.Image.FromStream(fs);
                }
                File.Delete(destPath);
            }
        }
    }
}