与 ProGuard 和 Linker 相关的神秘错误

你做了一个很棒的应用程序并在 Debug 中进行了测试,效果很好。一切都很好!

但是,你决定准备发布应用程序。你设置了 MultiDex,ProGuard 和 Linker,然后它就停止了工作。

本教程旨在帮助你找出可能导致神秘错误的 ProGuard 和 Linker 相关的常见问题。

了解 Xamarin.Linker

Xamarin.Linker 是构建过程中的一个工具,它可以从 .NET 代码中删除未使用的代码和类 (而不是 Java 代码) 。在项目的属性 - > Android 选项 - >链接器中,将有一个选项框链接选项:

StackOverflow 文档

:没有删除代码。

仅限 Sdk 程序集 :此选项使 Xamarin.Linker 仅在 Xamarin 库中检查未使用的代码。这个选项很安全。

Sdk 和用户程序集 :此选项使 Xamarin.Linker 检查 Xamarin 库和项目代码(包括 PCL,Xamarin 组件和 NuGet 包)中未使用的代码。这个选项并不总是安全的!

使用 Sdk 和用户程序集选项时,Xamarin.Linker 可能会认为部分代码在实际使用时非常未使用! 这可能会导致某些库停止正常工作并导致应用中出现错误。

要使 Xamarin.Linker 不删除代码,有 3 个选项:

  1. 将链接选项设置为或“仅限 Sdk 程序集”;
  2. 跳过链接组件;
  3. 使用保留属性。

示例 2.跳过链接程序集:

在下面的示例中,使用 Xamarin.Linker 导致 NuGet 包( Octokit )可以正常工作,因为它无法再连接到 Internet:

[0:] ERROR
[0:] SOURCE: mscorlib
[0:] MESSAGE: Object reference not set to an instance of an object.
[0:] STACK TRACE:   at Octokit.PocoJsonSerializerStrategy.DeserializeObject (System.Object value, System.Type type) [0x003d8] in D:\repos\octokit.net\Octokit\SimpleJson.cs:1472 
  at Octokit.Internal.SimpleJsonSerializer+GitHubSerializerStrategy.DeserializeObject (System.Object value, System.Type type) [0x001c3] in D:\repos\octokit.net\Octokit\Http\SimpleJsonSerializer.cs:165 
  at Octokit.SimpleJson.DeserializeObject (System.String json, System.Type type, Octokit.IJsonSerializerStrategy jsonSerializerStrategy) [0x00007] in D:\repos\octokit.net\Octokit\SimpleJson.cs:583 
  at Octokit.SimpleJson.DeserializeObject[T] (System.String json, Octokit.IJsonSerializerStrategy jsonSerializerStrategy) [0x00000] in D:\repos\octokit.net\Octokit\SimpleJson.cs:595 
  at Octokit.Internal.SimpleJsonSerializer.Deserialize[T] (System.String json) [0x00000] in D:\repos\octokit.net\Octokit\Http\SimpleJsonSerializer.cs:21 
  at Octokit.Internal.JsonHttpPipeline.DeserializeResponse[T] (Octokit.IResponse response) [0x000a7] in D:\repos\octokit.net\Octokit\Http\JsonHttpPipeline.cs:62 
  at Octokit.Connection+<Run>d__54`1[T].MoveNext () [0x0009c] in D:\repos\octokit.net\Octokit\Http\Connection.cs:574 
--- End of stack trace from previous location where exception was thrown ---

要使库再次开始工作,必须在 Skip links assemblies 字段中添加包引用名称,该字段位于项目 - > Properties - > Android Options - > Linker 中,如下图所示:

StackOverflow 文档

之后,库开始工作,没有任何问题。

示例 3.使用 Preserve 属性:

Xamarin.Linker 将未使用的代码视为项目核心中模型类的代码。

要在链接过程中保留类,可以使用保留属性。

首先,在项目核心中创建一个名为 PreserveAttribute.cs 的类,插入以下代码并将命名空间替换为项目的命名空间:

PreserveAttribute.cs:

namespace My_App_Core.Models
{
    public sealed class PreserveAttribute : System.Attribute
    {
        public bool AllMembers;
        public bool Conditional;
    }
}

在项目核心的每个模型类中,插入 Preserve 属性,如下例所示:

Country.cs:

using System;
using System.Collections.Generic;

namespace My_App_Core.Models
{
    [Preserve(AllMembers = true)]
    public class Country
    {
        public String name { get; set; }
        public String ISOcode { get; set; }

        [Preserve(AllMembers = true)]
        public Country(String name, String ISOCode)
        {
            this.name = name;
            this.ISOCode = ISOCode;
        }
    }
}

之后,链接过程将不再删除保留的代码。

了解 ProGuard

ProGuard 是构建过程中的一个工具,可以从 Java 代码中删除未使用的代码和类。它还混淆和优化代码。

但是,ProGuard 有时可能会删除它认为未使用的代码。为避免这种情况,开发人员必须调试应用程序(在 Android 设备监视器和 Visual Studio 调试中)并检测已删除的类,然后配置 ProGuard 配置文件以保留该类。

在下面的示例中,ProGuard 删除了 AXML 布局文件中使用的两个类(Android.Support.V7.Widget.FitWindowsLinearLayout 和 Android.Support.Design.Widget.AppBarLayout),但在代码中被视为未使用。在呈现活动布局时,删除导致 Java 代码中的 ClassNotFoundException:

layout_activitymain.axml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activitymain_drawerlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true" <!-- ### HERE ### -->
    tools:openDrawer="start">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">
        <!-- ### HERE ## -->
        <android.support.design.widget.AppBarLayout
            android:id="@+id/activitymain_appbarlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
...

在 SetContentView 中创建布局时 LogCat 显示错误:

StackOverflow 文档

要修复此错误,必须将以下行添加到项目的 ProGuard 配置文件中:

-keep public class android.support.v7.widget.FitWindowsLinearLayout
-keep public class android.support.design.widget.AppBarLayout

之后,创建布局时不再显示错误。

ProGuard 警告

在构建项目后,ProGuard 有时会在错误列表中显示警告。虽然他们提出了一个问题,即你的应用程序是否正常,但并非所有警告都表明存在问题,特别是如果你的应用程序成功构建。

一个例子是使用 Picasso 库时:使用 ProGuard 时,这可能会显示 okio.Okio: can't find referenced class (...)can't write resource [META-INF/MANIFEST.MF] (Duplicate zip entry [okhttp.jar:META-INF/MANIFEST.MF]) (...) 等警告,但应用程序构建和库可以正常工作。