Страницы

Поиск по вопросам

вторник, 13 ноября 2018 г.

Android Studio. После изменения хотя бы одной строчки поведение приложения не меняется. Это нормально?

В приложении есть несколько Activity. Есть кнопка, у неё установлен вот такой ClickListener:
private View.OnClickListener buttonClickListener = new View.OnClickListener() { @Override public void onClick(View v) { button.setText("Hello!"); //button.setText("Bye!"); } };
Нажимаю Debug . Приложение запускаю(дебажу) на телефоне.
В приложении изначальный текст на кнопке "Welcome". После нажатия на кнопку текст меняется на "Hello!". Всё хорошо.
Раскоменчиваю строчку //button.setText("Bye!"); и нажимаю Debug
После нажатия на кнопку текст меняется на "Hello!". Почему? Ставлю breakpoint на button.setText("Hello!"); нажимаю ещё раз ловится breakpoint нажимаю F8 и указатель пролетает мимо button.setText("Bye!");. Это нормально?
Помогает только Rebuild Project.
Я не заметил с какого момента это стало проявляться, но сейчас так постоянно. Возможно, что это нормально и нужно просто где-то поставить галочку чтобы Android Studio делала Rebuild Project по нажатию на Run и Debug.
UPD: Версия студии
Android Studio 3.1 Build #AI-173.4670197, built on March 22, 2018 JRE: 1.8.0_152-release-1024-b02 amd64 JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o Windows 7 6.1


Ответ

Это баг в новой версии студии 3.1 (точнее - в проектах, обновлённых до новой версии). Из конфигурации скрипта запуска пропала инструкция пересборки проекта при запуске. Чтобы это починить надо открыть Run > Edit Configuration и посмотреть что есть под Before launch:, если там нет Gradle-aware Make как на картинке:

то добавьте это, в меню создания оставив пустым поле с заданием. В итоге получится так:

Перезапуск SVG анимации через удаление animateTransform

Всем привет. Возникла проблема с svg анимацией. Мне нужно чтобы js останавливал анимацию круга ( для этого я удаляю тег ), а затем возобновлял ее ( возвращаю тег обратно), и анимация начиналась либо с начала, либо, что еще лучше, с того места где остановилась.
Но при возвращении тега анимация продолжается с того момента, где был бы круг если бы я не удалял анимацию. Собственно вопрос в том, что я делаю неправильно.
Заранее спасибо!
const circle = document.getElementsByTagName('circle')[0], animate = ``; let b = true; setInterval(() => { b ? circle.innerHTML = '' : circle.innerHTML = animate; b = !b }, 1000);


Ответ

Добавляем кнопки "GO" и "STOP" старта и остановки анимации. На которые вешаем события onclick='start()' и onclick='pause()'
GO STOP
Добавляем в анимацию команду запуска begin="gO1.click" при клике на кнопке с идентификатором id="gO1" Добавляем скрипт, который управляет остановкой анимации и запуска её вновь с остановленного места.
Ниже полный код приложения:
let flag = 0, svg = document.querySelector('svg'); // Необходимо поймать второй клик (если на первом удалить атрибуты, анимация не начнётся), потом подменяем функцию на вызов по "разморозке" анимации let start = function(){ if(flag === 1){ // Убираем у animateTransform триггер, чтобы не сбрасывалась анимация на ноль Array.from(svg.querySelectorAll('animateTransform')).forEach(e => e.removeAttribute('begin')); start = _ => svg.unpauseAnimations(); start(); } flag++; } const pause = function(){ svg.pauseAnimations(); } GO STOP
Скрипт и комментарии к нему не мои. Особая благодарность @Other, который в своё время нашёл его на просторах интернета.
Второй пример анимации отрисовки спирали
let flag = 0, svg = document.querySelector('svg'); let start = function(){ if(flag === 1){ Array.from(svg.querySelectorAll('animateMotion')).forEach(e => e.removeAttribute('begin')); start = _ => svg.unpauseAnimations(); start(); } flag++; } const pause = function(){ svg.pauseAnimations(); } GO STOP
Третий пример анимации с animateMotion
let flag = 0, svg = document.querySelector('svg'); let start = function(){ if(flag === 1){ Array.from(svg.querySelectorAll('animateMotion')).forEach(e => e.removeAttribute('begin')); start = _ => svg.unpauseAnimations(); start(); } flag++; } const pause = function(){ svg.pauseAnimations(); } GO STOP

Entity Framework создаёт лишнюю таблицу

К примеру, имеется у меня контекст из двух сетов:
public class ConfigurationContext : DbContext { public ConfigurationContext() : base("ConfigurationContext") { Database.SetInitializer(new MigrateDatabaseToLatestVersion()); }
public DbSet Tags { get; set; } public DbSet Sources { get; set; } }
Таблица успешно генерируется, но почему-то содержит вместо двух таблиц - три! При этом у меня нет в проекте никаких сущностей, которые имели бы имя User, свойство Age или связь с такими объектами.

Эта лишняя таблица следует за мной повсюду. Стоит мне создать новый проект, изменить строку подключения, название или содержимое классов - она всегда создаётся.
В данный момент классы не содержат никаких свойств, кроме intового ключа. Таблица Users всё равно создается при каждой миграции.
Класс Configuration, отвечающий за миграцию также привожу ниже:
internal sealed class Configuration : DbMigrationsConfiguration { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } }
Как ликвидировать лишнюю таблицу?


Ответ

Эту таблицу создает вовсе не Entity Framework, она создается сама при создании базы данных.
Дело в том, что в MS SQL Server есть такая штука как системная БД под названием model. При создании новой БД в нее копируются все объекты из model, и если в model есть какая-то таблица - она будет вас вот так преследовать. Просто удалите таблицу из model и больше не создавайте ее там.
Аналогичное поведение возможно и в PostgreSQL, там в таких случаях нужно проверить БД template1.

Почему следующая операция выдает -1?

double m = (byte) 110_987_654_6299.123_34;


Ответ

Справедливости ради стоит отметить, что результатом (значением m) будет -1.0, а не -1
Происходят 3 действия:
double число 110_987_654_6299.123_34 приводится к int. Результатом будет 2147483647 int число 2147483647 приводится к byte. Результатом будет -1 byte число -1 приводится к double. Результатом будет -1.0
Почему приведение работает именно так - можно прочитать в документации. Если вкратце, то:
при приведении double к byte (правая часть выражения) сначала происходит приведение double к int, и только затем - int к byte приведение double к int в случае превышения допустимого int значения возвращает максимальное значение int приведение int к byte просто берёт последние 8 бит из int

Поведенческий полиморфизм подтипов

Цитата из википедии: Наследование, — даже в языках программирования, которые поддерживают применение наследования как механизма, обеспечивающего полиморфизм подтипов, — не гарантирует поведенческий полиморфизм подтипов; смотри: «Принцип подстановки» Барбары Лисков.
Что же здесь подразумевают под поведенческим полиморфизмом подтипов? Как грамотно и лаконично ответить на этот вопрос?
Я же понимаю это так: Опять же цитата из википедии: Более простыми словами можно сказать, что поведение наследуемых классов не должно противоречить поведению, заданному базовым классом, то есть поведение наследуемых классов должно быть ожидаемым для кода, использующего переменную базового типа. Из этого следует, что наследование не защищает программиста, от того, что он может изменить поведение в переопределенном методе. Например метод add() в базовом классе добавляет объект, а его можно легко переопределить и этот метод станет удалять некий объект. Сохранение поведения базового метода и есть поведенческий полиморфизм.
Прав ли я?


Ответ

Вы все верно понимаете. Поведенческий полиморфизм это когда дочерний класс не меняет поведения родительского. Например представьте ситуацию, у нас есть класс
class Rechtangle { protected int width; protected int height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
public int area() { return width * height; } }
И мы добавляем новый класс "Квадрат", чем он отличается от прямоугольника, тем что его стороны должны изменяться одновременно, если мы меняем высоту, то и должна меняться ширина, пример
class Square extends Rechtangle { @Override public void setWidth(int w) { this.width = w; this.height = w; }
@Override public void setHeight(int h) { this.width = h; this.height = h; } }
А теперь представьте, что какой-то программист написал следующий код
void mainProcedure(Rechtangle figure) { figure.setWidth(5); figure.setHeight(3);
assert figure.area() == 15; }
Давайте теперь вызовем эту функцию с реализацией квадрата
mainProcedure(new Square())
Будет выброшено AssertionError, вот вам наглядное нарушение принципа LSP и поведенческого полиморфизма. Потому что фактически квадрат это не прямоугольник и разработчик процедуры mainProcedure не ожидал что он может получить квадрат маскирующийся за прямоугольником. Программист который написал процедуру прав. Он проверил контракт который должен был соблюдаться прямоугольником, но был нарушен квадратом.

Как работает данная XXE атака?

Здесь репорт, демонстрирующий атаку XXE.
Если вкратце, атакующий разместил у себя на сервере xml
]> &localfile;&remotedoc; email settings SMTP 52.34.103.214 1191 off account@obmhld.com yandex.ru off off off
в определённой директории, и после этого попытался авторизоваться на сайте mail.ru со следующими данными:
@
И после этого с мэйловского сервера прилетают запросы на сервер атакующего:
1:
5.61.237.44 - - [14/Feb/2018:01:05:14 +0000] "GET /autodiscover/autodiscover.xml HTTP/1.0" 200 955 "http://obmhld.com/autodiscover/autodiscover.xml" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Win64; x64; Trident/6.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3; Tablet PC 2.0; Microsoft Outlook 15.0.4481; ms-office; MSOffice 15)"
2:
5.61.237.44 - - [14/Feb/2018:01:05:15 +0000] "GET /pocs/?token=xmlsdfgdg5454g54&doc1=2 HTTP/1.0" 200 10 "-" "-"
Хотелось бы понять механизм работы этой чудной атаки. Спасибо!


Ответ

Атака состоит из двух частей.
В первой части Mail.ru пытается узнать данные для получения почты с неизвестного домена, загружая autodiscover.xml. Понятно, зачем это им нужно: можно сразу дать человеку все данные для настройки сбора почты, останется только ввести пароль. Сам механизм известный, в нём самом каких-то проблем. Примерный вид файла. Не исключаю что они до сих пор запрашивают этот файл.
Во второй части начинается интересное: парсер Mail.ru загружает внешние сущности (вида   или &custom;), которые объявляются в начале документа:

В общем случае можно вставить сущность прямо в документ, получим содержимое файла, на которую она ссылается, прямо на экран. В данном случае это не работает, но всё равно можно увидеть получается распарсить файл или нет. Например, если первая сущность &localfile; ссылается на несуществующий файл, то вторая сущность &remotedoc; не приведет к HTTP запросу на сервер атакующего. Если же файл есть, то будет запрос к серверу атакующего.
Если говорить об общем случае, то возьмём такой XML:
]> &hostname;
Пропустим его через xmllint:
xmllint --noent xxe.xml
И увидим у себя на экране имя вашего компьютера:
mysuperhostname
Как защититься от этого? Отключить загрузку внешних сущностей. Например, в PHP для этого есть функция libxml_disable_entity_loader(). Документация.

Событие на каждом элементе

Решил сделать такой эффект("живой" пример):

Цель: У меня есть три элемента и мне нужно, чтобы на каждом элементе срабатывало это событие(показанное выше). Пробовал в цикле, но всё тщетно.
var el = document.querySelector('.magnetic-button'); var options = { ease: 0.1, magneticForce: 0.6 }; var mouse = { x: 0, y: 0 }; var pos = { cx: 0, cy: 0, tx: 0, ty: 0, x: 0, y: 0 }; var sizes = el.getBoundingClientRect(); observe(); update(); function observe() { window.addEventListener('resize', resizeHandler, false); el.addEventListener('mousemove', mouseMoveHandler, false); el.addEventListener('mouseleave', mouseLeaveHandler, false); } function resizeHandler() { sizes = el.getBoundingClientRect(); } function mouseMoveHandler(e) { //sizes mouse.x = e.pageX; mouse.y = e.pageY; // center pos.cx = sizes.left + sizes.width / 2; pos.cy = sizes.top + sizes.height / 2; // dist var distX = mouse.x - sizes.left - sizes.width / 2; var distY = mouse.y - sizes.top - sizes.height / 2; pos.tx = distX - distX * (1 - options.magneticForce); pos.ty = distY - distY * (1 - options.magneticForce); } function mouseLeaveHandler(e) { pos.tx = 0; pos.ty = 0; } function render() { pos.x += (pos.tx - pos.x) * options.ease; pos.y += (pos.ty - pos.y) * options.ease; var transform2 = 'translateX(' + pos.x + 'px) translateY(' + pos.y + 'px)'; el.style.webkitTransform = transform2; el.style.MozTransform = transform2; el.style.msTransform = transform2; el.style.OTransform = transform2; el.style.transform = transform2; } function update() { render(); window.requestAnimationFrame(update); } .social-nets { display: flex; } .magnetic-button { margin-left: 1rem; position: relative; width: 3.3333333333rem; height: 3.3333333333rem; background-color: #b3b3b3; color: rgba(0, 0, 0, 0.8); border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; } .magnetic-button::before, .magnetic-button:after { content: ""; position: absolute; width: 100%; height: 100%; border-radius: 50%; transition: transform 800ms cubic-bezier(0.19, 1, 0.22, 1) 0ms; will-change: transform; z-index: -1; } .magnetic-button::before { background-color: red; } .magnetic-button:hover::before { transform: scale(0.95); } .magnetic-button:hover::after { transform: scale(0.85); }



Ответ

Мышь - одна, опции - тоже одни на всех. Но размер и позиция у каждого свои.
var options = { ease: 0.1, magneticForce: 0.6 }; var mouse = { x: 0, y: 0 }; observe(); update(); function observe() { window.addEventListener('resize', resizeHandler, false); var els = document.querySelectorAll('.magnetic-button'); for(var i = 0; i < els.length; i++) { els[i].addEventListener('mousemove', mouseMoveHandler, false); els[i].addEventListener('mouseleave', mouseLeaveHandler, false); els[i].pos = { cx: 0, cy: 0, tx: 0, ty: 0, x: 0, y: 0 }; els[i].sizes = els[i].getBoundingClientRect(); } } function resizeHandler() { var els = document.querySelectorAll('.magnetic-button'); for(var i = 0; i < els.length; i++) { els[i].sizes = els[i].getBoundingClientRect(); } } function mouseMoveHandler(e) { //sizes mouse.x = e.pageX; mouse.y = e.pageY; // center this.pos.cx = this.sizes.left + this.sizes.width / 2; this.pos.cy = this.sizes.top + this.sizes.height / 2; // dist var distX = mouse.x - this.sizes.left - this.sizes.width / 2; var distY = mouse.y - this.sizes.top - this.sizes.height / 2; this.pos.tx = distX - distX * (1 - options.magneticForce); this.pos.ty = distY - distY * (1 - options.magneticForce); } function mouseLeaveHandler(e) { this.pos.tx = 0; this.pos.ty = 0; } function render(el) { el.pos.x += (el.pos.tx - el.pos.x) * options.ease; el.pos.y += (el.pos.ty - el.pos.y) * options.ease; var transform2 = 'translateX(' + el.pos.x + 'px) translateY(' + el.pos.y + 'px)'; el.style.webkitTransform = transform2; el.style.MozTransform = transform2; el.style.msTransform = transform2; el.style.OTransform = transform2; el.style.transform = transform2; } function update() { var els = document.querySelectorAll('.magnetic-button'); for(var i = 0; i < els.length; i++) { render(els[i]); } window.requestAnimationFrame(update); } .social-nets { display: flex; } .magnetic-button { margin-left: 1rem; position: relative; width: 3.3333333333rem; height: 3.3333333333rem; background-color: #b3b3b3; color: rgba(0, 0, 0, 0.8); border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; } .magnetic-button::before, .magnetic-button:after { content: ""; position: absolute; width: 100%; height: 100%; border-radius: 50%; transition: transform 800ms cubic-bezier(0.19, 1, 0.22, 1) 0ms; will-change: transform; z-index: -1; } .magnetic-button::before { background-color: red; } .magnetic-button:hover::before { transform: scale(0.95); } .magnetic-button:hover::after { transform: scale(0.85); }