Другим случаем, когда NullReferenceExceptions
может случиться, является (неправильное) использование оператора as
:
class Book {
public string Name { get; set; }
}
class Car { }
Car mycar = new Car();
Book mybook = mycar as Book; // Incompatible conversion --> mybook = null
Console.WriteLine(mybook.Name); // NullReferenceException
Здесь Book
и Car
являются несовместимыми типами; a Car
не может быть преобразован / передан в Book
. Когда этот сбой завершается неудачно, as
возвращает null
. Используя mybook
после этого, вы вызываете NullReferenceException
.
В общем случае вы должны использовать cast или as
, как показано ниже:
Если вы ожидаете преобразования типа в всегда преуспевает (т. е. вы знаете, какой объект должен быть впереди времени), тогда вы должны использовать cast:
ComicBook cb = (ComicBook)specificBook;
Если вы не уверены в типе, но хотите попробовать , чтобы использовать его как определенный тип, затем используйте as
:
ComicBook cb = specificBook as ComicBook;
if (cb != null) {
// ...
}
Хороший способ вставить часто используемую строку (например, имя приложения) в xml без использования кода Java: source
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
<!ENTITY appname "MyAppName">
<!ENTITY author "MrGreen">
]>
<resources>
<string name="app_name">&appname;</string>
<string name="description">The &appname; app was created by &author;</string>
</resources>
UPDATE:
Вы даже можете определить свою сущность глобально, например:
res / raw / entities.ent:
<!ENTITY appname "MyAppName">
<!ENTITY author "MrGreen">
res / values / string .xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
<!ENTITY % ents SYSTEM "./res/raw/entities.ent">
%ents;
]>
<resources>
<string name="app_name">&appname;</string>
<string name="description">The &appname; app was created by &author;</string>
</resources>
Я знаю, что это более старая должность, но я хотел поделиться быстрым «грязным решением», которое я придумал для моего проекта. Он работает только для TextViews, но может быть адаптирован к другим виджетам. Обратите внимание: для этого требуется, чтобы ссылка была заключена в квадратные скобки (например, [@string/foo]
).
public class RefResolvingTextView extends TextView
{
// ...
@Override
public void setText(CharSequence text, BufferType type)
{
final StringBuilder sb = new StringBuilder(text);
final String defPackage = getContext().getApplicationContext().
getPackageName();
int beg;
while((beg = sb.indexOf("[@string/")) != -1)
{
int end = sb.indexOf("]", beg);
String name = sb.substring(beg + 2, end);
int resId = getResources().getIdentifier(name, null, defPackage);
if(resId == 0)
{
throw new IllegalArgumentException(
"Failed to resolve link to @" + name);
}
sb.replace(beg, end + 1, getContext().getString(resId));
}
super.setText(sb, type);
}
}
Недостатком этого подхода является то, что setText()
преобразует CharSequence
в String
, что является проблемой, если вы передаете такие вещи, как SpannableString
; для моего проекта это не было проблемой, так как я использовал его только для TextViews
, что мне не нужно было получать доступ с моего Activity
.
В Android вы не можете конкатенировать строки внутри xml
Ниже не поддерживается
<string name="string_default">@string/string1 TEST</string>
Проверьте эту ссылку ниже, чтобы узнать, как ее достичь
Вы можете использовать заполнители строк (% s) и заменить их с помощью java во время выполнения
<resources>
<string name="button_text">Add item</string>
<string name="message_text">Custom text %s </string>
</resources>
и в java
String final = String.format(getString(R.string.message_text),getString(R.string.button_text));
, а затем установить его на где он использует строку
С новой привязкой данных вы можете объединить и сделать гораздо больше в вашем xml.
, например, если вы получили сообщение1 и сообщение2, вы можете:
android:text="@{@string/message1 + ': ' + @string/message2}"
вы даже можете импортировать некоторые текстовые утилиты и вызывать String.format и друзей.
, к сожалению, если вы хотите повторно использовать его в нескольких местах, это может стать беспорядочным, вы не хотите, чтобы этот фрагмент кода повсюду , и вы не можете определить их в xml в одном месте (не то, что я знаю), поэтому для этого вы можете создать класс, который будет инкапсулировать эти композиции:
public final class StringCompositions {
public static final String completeMessage = getString(R.string.message1) + ": " + getString(R.string.message2);
}
, тогда вы можете использовать его вместо этого ( вам нужно будет импортировать класс со связыванием данных)
android:text="@{StringCompositions.completeMessage}"
В дополнение к вышеприведенному ответу Франческо Лауриты https://stackoverflow.com/a/39870268/9400836
Кажется, что существует ошибка компиляции "& entity; ссылается, но не объявляется ", который может быть решен путем ссылки на внешнее объявление, подобное этому
res / raw / entities.ent
<!ENTITY appname "My App Name">
res / values / strings.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
<!ENTITY appname SYSTEM "/raw/entities.ent">
]>
<resources>
<string name="app_name">&appname;</string>
</resources
Хотя он компилируется и запускается, он имеет пустое значение. Может быть, кто-то знает, как это решить. Я бы опубликовал комментарий, но минимальная репутация - 50.
Можно ссылаться друг на друга, пока вся ваша строка состоит из ссылочного имени. Например, это будет работать:
<string name="app_name">My App</string>
<string name="activity_title">@string/app_name</string>
<string name="message_title">@string/app_name</string>
Это еще более полезно для установки значений по умолчанию:
<string name="string1">String 1</string>
<string name="string2">String 2</string>
<string name="string3">String 3</string>
<string name="string_default">@string/string1</string>
Теперь вы можете использовать string_default
всюду в своем коде, и вы можете легко изменить значение по умолчанию в любое время.
Вы можете использовать свою собственную логику, которая рекурсивно решает вложенные строки.
/**
* Regex that matches a resource string such as <code>@string/a-b_c1</code>.
*/
private static final String REGEX_RESOURCE_STRING = "@string/([A-Za-z0-9-_]*)";
/** Name of the resource type "string" as in <code>@string/...</code> */
private static final String DEF_TYPE_STRING = "string";
/**
* Recursively replaces resources such as <code>@string/abc</code> with
* their localized values from the app's resource strings (e.g.
* <code>strings.xml</code>) within a <code>source</code> string.
*
* Also works recursively, that is, when a resource contains another
* resource that contains another resource, etc.
*
* @param source
* @return <code>source</code> with replaced resources (if they exist)
*/
public static String replaceResourceStrings(Context context, String source) {
// Recursively resolve strings
Pattern p = Pattern.compile(REGEX_RESOURCE_STRING);
Matcher m = p.matcher(source);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String stringFromResources = getStringByName(context, m.group(1));
if (stringFromResources == null) {
Log.w(Constants.LOG,
"No String resource found for ID \"" + m.group(1)
+ "\" while inserting resources");
/*
* No need to try to load from defaults, android is trying that
* for us. If we're here, the resource does not exist. Just
* return its ID.
*/
stringFromResources = m.group(1);
}
m.appendReplacement(sb, // Recurse
replaceResourceStrings(context, stringFromResources));
}
m.appendTail(sb);
return sb.toString();
}
/**
* Returns the string value of a string resource (e.g. defined in
* <code>values.xml</code>).
*
* @param name
* @return the value of the string resource or <code>null</code> if no
* resource found for id
*/
public static String getStringByName(Context context, String name) {
int resourceId = getResourceId(context, DEF_TYPE_STRING, name);
if (resourceId != 0) {
return context.getString(resourceId);
} else {
return null;
}
}
/**
* Finds the numeric id of a string resource (e.g. defined in
* <code>values.xml</code>).
*
* @param defType
* Optional default resource type to find, if "type/" is not
* included in the name. Can be null to require an explicit type.
*
* @param name
* the name of the desired resource
* @return the associated resource identifier. Returns 0 if no such resource
* was found. (0 is not a valid resource ID.)
*/
private static int getResourceId(Context context, String defType,
String name) {
return context.getResources().getIdentifier(name, defType,
context.getPackageName());
}
Например, из Activity
вызывает его так
replaceResourceStrings(this, getString(R.string.message_text));
Я создал простой плагин gradle , который позволяет ссылаться на одну строку от другой. Вы можете ссылаться на строки, которые определены в другом файле, например, в другом варианте сборки или в библиотеке. Недостатки этого подхода - репозиторий IDE не найдет таких ссылок.
Используйте синтаксис {{string_name}}
для ссылки на строку:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="super">Super</string>
<string name="app_name">My {{super}} App</string>
<string name="app_description">Name of my application is: {{app_name}}</string>
</resources>
Чтобы интегрировать плагин, просто добавьте следующий код в вашем файле уровня приложения или библиотечного модуля build.gradle
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.android-text-resolver:buildSrc:1.2.0"
}
}
apply plugin: "com.icesmith.androidtextresolver"
UPDATE: библиотека не работает с плагином Android 3.0 и выше, потому что новая версия плагина использует aapt2, который упаковывает ресурсы в .flat, поэтому упакованные ресурсы недоступны для библиотеки. В качестве временного решения вы можете отключить aapt2, установив в файле gradle.properties файл android.enableAapt2 = false.
Я думаю, вы не можете. Но вы можете «форматировать» строку по своему усмотрению:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="button_text">Add item</string>
<string name="message_text">You don't have any items yet! Add one by pressing the %1$s button.</string>
</resources>
В коде:
Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
res.getString(R.string.button_text));