與 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]) (...) 等警告,但應用程式構建和庫可以正常工作。