Страницы

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

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

Тормозит браузер, возможные причины?

Приложение написано на PHP + JAVASCRIPT
На странице отображается около 2000 строк и более (если отображается мало - около 100, то никаких проблем); В таблице в коде строки задействованы несколько style=".." , class="..", id="..", onclick=".." Так же на экране кроме таблицы содержатся элементы с атрибутом стиля (position:fixed), не знаю может это тоже влияет на что-то?
JavaScript отслеживает скроллинг экрана, для выполнения некоторых функций (добавить кнопку "Наверх", подгрузить еще строки при прокрутке вниз...)
Так вот при отображении такого количества строк на экране браузер начинает ощутимо тормозить..
После долгих поисков сложилось мнение, что при большом количества элементов тормозить могут:
функции JavaScript которые пересчитывают большое количество элементов на экране, теги style="" может быть элементы со стилем position:fixed..
Для того чтобы устранить крайнее предположение убрал таблицу с заказами в блок фиксированных размеров и Overflow-y:auto; В результате чего тормозить стало ЕЩЕ БОЛЬШЕ ! :))
Может кто-нибудь сталкивался с подобной проблемой? Может быть есть список явлений и конструкций, которые могут ощутимо тормозить работу браузера ??? Помогите ))
UPDATE: Сделал запись Timeline, во время скроллинга данной страницы.
Увеличенный участок:
ВОТ ПРИМЕР КОДА СТРОКИ ИЗ ТАБЛИЦЫ:

№ 133334
Новый
Новый 333 В производстве 123123 В пути 123123 123123 Произведен 123123 555 123123 Временной Прозвонен Выполнен Списан
Сотрудник
1112 Сергей Щербинка Даниёр александр подольск Рустам Дима МишаКино Коля Андрейка Олег Дима Самсон Лазиз Олег Темка Андрей Сержант Район Никита Аброр Миша Вальдемар Саша пеший Сергей Пушкинская Ваня Марина Валера Ванюшка Алам Артем Гера Максим Паша САМОВЫВОЗ Уася
Щербинка
Щербинка Бутово Ясенево Коммунарка Подольск Тестовый филиал
Основная
Основная 123 333 43
!!!
80 мм Товарный Два тов. 50 мм Сборка

На самом деле это только часть строки, но это уже дает ощутимые последствия, когда таких строк на экране много...
UPD: После решения ряда проблем с тормозами, столкнулся с новой неизвестной: даже при условии что блок с таблицей (с большим контентом) не виден на экране (display:none), при вызове элементарной функции alert('123'), появляются тормоза, которые в Timeline отражаются как Other, вот скрин:
Причем! Если данный контент виден на экране и в обертке нет стиля display:none; функция alert() выполняется быстро, как положено )) Удивительно!


Ответ

Попробуйте сделать такие шаги:
Постарайтесь избавится от лишнего поиска элементов в dom
плохо:
for(row in table){ $("selector").find("selector2").css("..."); }
хорошо:
var _$elem = $("selector").find("selector2");
for(row in table){ _$elem.css("..."); } Убедитесь, что, когда вы навешиваете обработчик события, вы его потом убираете. Следить за этим нужно, чтобы вдруг так не оказалось, что скролл слушают сразу несколько обработчиков или что обработчики продублировались.
Например:
//устанавливаем var eventListener = el.addEventListener("click", eventHandler, false);
//когда не нужен удаляем el.removeEventListener('click', eventListener, false); Постарайтесь избавится от лишнего вызова тяжелых функций и рендеринга при скролле. Например, слушайте только каждый десятый скролл или отрисовывайте не все, а только то, что действительно поменялось. Пожмите картинки. Даже если вы делаете export for web в фотошопе, то например tinyPng жмет их еще сильнее (Только осторожней с градиентами, следите чтобы не сильно побились) Подумайте в сторону модульной организации кода на клиенте. Например RequireJs или React-Redux или что-нибудь в этом духе.

Повторное использование объекта std::future

Можно ли присваивать новое значение объекту std::future, не получив результата выполнения предыдущей задачи?
Например:
std::future f; // глобальная переменная
TResult thread_func(params) { /* тут какие-то тяжелые вычисления */ }
// запрос "долгой" операции (может поступить до окончания предыдущего расчета!) void RequestAsync() { f = std::async(thread_func, some_params); }
// проверяем получение результата (например, таймером) void CheckResult() { if (!f.valid()) return;
TResult result = f.get(); }
Я тестировал этот код и не получил никаких ошибок, но сомнения в корректности такого присваивания остаются. К примеру, что будет, если несколько запущенных потоков вернут значение параметра одновременно? Не возникнет ли утечка памяти, при попытке потока вернуть значение через уже перезаписанную async'ом переменную?


Ответ

Это Вы думаете, что Ваш код так работает. И, в целом, это логично. К сожалению, реальность такова, что код работает по другому. Вот как работает эта строчка:
f = std::async(thread_func, some_params);
Если в f сохранена future предыдущего запуска async, то только после того, как завершится предыдущая операция, новый объект future будет помещён в f. Таким образом, при таком подходе, не более 2-х одновременно выполняемых операций может быть, и никогда в f нельзя будет поместить новый объект, пока старый не завершился.

c# WinForms: проблемы с вызовом конекстного меню после добавления Drag-Drop

Проблема с запуском контекстного меню у объекта TreeView на форме. После того, как пользователь левой кнопкой выделил элемент в дереве, ему нужно два раза жать на правую кнопку мыши, чтобы появилось контекстное меню. Клиента это очень раздражает.
Проблема появилась после того, как treeView на форме стал использоваться, как источник данных в процедуре перетаскивания мышью Drag-Drop, реализованными стандартными средствами MicroSoft.
В обработчик нажатия был добавлен следующий код:
private void treeView_MouseDown(object sender, MouseEventArgs e) { if (e.Button == System.Windows.Forms.MouseButtons.Left) { TreeNode node = treeView.GetNodeAt(e.X, e.Y);
if (node != null) { // ... некоторые предварительные вычисления, // не влияющие на элементы формы, а меняющие Tag у node
//Проблемы появились после добавления этой стандартной функции от MicroSoft treeView.DoDragDrop(node, DragDropEffects.Copy);
//Это старый код, который должен остаться и при котором все работало treeView.SelectedNode = node; } } }
Сам Drag-Drop отрабатывается без проблем.
Если важно, то на treeView_AfterSelect реализован обработчик, заполняющий элементы формы информацией
private void treeView_AfterSelect(object sender, TreeViewEventArgs e) { TreeNode currentNode = treeView.SelectedNode; if (currentNode != null) { FillAll(currentNode); } }
Подскажите, пожалуйста как решить эту проблему без реализации собственного Drag-Drop, либо как объяснить клиенту, что это не баг в реализации DoDragDrop() MicroSoft-ом, а фича.
Детальное изучение это редкого явления показало, что для останова процедуры DoDragDrop() недостаточно просто отпустить левую кнопку мыши. Обработчик treeView_MouseUp() в случае использования стандартного DoDragDrop() на него не отзывается.
UpDate от 26.12.2016: По запросу добавляю код, обрабатывающий события процедуры DragDrop:
1) На событие DragEnter для контрола ListBox lstBxForSearch; , который является приемником DragDrop повешен следующий обработчик:
private void lstBxForSearch_DragEnter(object sender, DragEventArgs e) { // Предварительно сбрасываем флаг допустимости использования контрола lstBxForSearch, // как приемника процедуры DragDop e.Effect = DragDropEffects.None;
// Устанавливаем этот флаг, если объект, который тянем, может быть принят // контролом lstBxForSearch, то есть источником служит контрол treeView if (e.Data.GetDataPresent(typeof(TreeNode))) { TreeNode node = e.Data.GetData(typeof(TreeNode)) as TreeNode; if (node != null && node.TreeView == treeView) { //Допустимый источник e.Effect = DragDropEffects.Copy; } } }
2) На событие DragDrop для ListBox lstBxForSearch; повешен такой обработчик:
private void lstBxForSearch_DragDrop(object sender, DragEventArgs e) { //Анализируем, что принесли на мыше TreeNode node = e.Data.GetData(typeof(TreeNode)) as TreeNode; if (node == null) return; addPlantToSearchList(node); }
и соответственно
private void addPlantToSearchList(TreeNode currentNode) { PlantExtendTag tg = currentNode.Tag as PlantExtendTag; if (tg == null) return;
MyDS.PlantShort_ViewRow plantRow = tg.PlantRow;
if ((TacsonomyLeve)plantRow.LevelNum == TacsonomyLeve.Kind) { lstBxPlantsForSearch.Items.Add(new NameId(plantRow.Id, currentNode.Text)); } else { toolStripStatusLabel.Text = "Расширенный поиск предусмотрен только по пересечению видов"; } }


Ответ

У TreeView есть специальное событие TreeView.ItemDrag. Это событие срабатывает, когда после мы начинаем тянуть элемент с зажатой клавишей мыши (можно настроить какой именно клавишей). При обычном клике оно не срабатывает. Подписываемся на него, и в нем начинаем процедуру перетаскивания. Например так:
private void treeView1_ItemDrag(object sender, ItemDragEventArgs e) { if(e.Button == MouseButtons.Left) { treeView1.DoDragDrop(e.Item, DragDropEffects.Move); } }
Теперь можно убрать вызов DoDragDrop из обработчика MouseDown и забыть про багофичу с правым кликом.

Это не единственный сюрприз от DoDragDrop. После его активации перестают обрабатываться все события мыши до его завершения. Скорее всего это связано с тем, что события мыши передаются оконному менеджеру, а не окну вашей программы, т.к. DragDrop может перетаскивать объекты между программами, а для этого нужен внешний контекст. Но глубоко в WinAPI я не копал, так что пожалуй это не плохая тема для отдельного вопроса

Лучший способ указывать отдельные CSS правила для разных браузеров

Так, как в разных браузерах, например, шрифты и отступы отображаются по-разному, нужно для каждого браузера указывать отдельное Css правило.
На данный момент использую следуее:
// IE9+ @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) .div1 transform scaleX(.7)
// Firefox _:-moz-tree-row(hover), .div1 transform scaleX(1)
//chrome .selector:not(*:root), .div1 transform scaleX(.4)
Есть ли лучший, более эффективный и компактный способ для этого? Есть ли еще специальные подобные правила, кроме этих трех, для других браузеров?


Ответ

Отдельные файлы для каждого браузера и их подключение через JavaScript
Я бы сделал отдельный css-файл для каждого браузера, затем с помощью JavaScript определял браузер клиента, затем с помощью JavaScript подключал соответствующий файл (по сути создаём тэг link).
Если правил не так и много, то можно с помощью JavaScript создать тэг style и его контент (предварительно определив браузер клиента).
Чистый CSS: условные стили для браузера (хаки)
Если не желаете связываться с JavaScript, есть интересное решение в рамках CSS на англ SO
Суть его в том, что вы указываете стили для каждого браузера инвидувиально через вендорные префиксы, @media или @supports
Под .selector подразумевается нужный вам селектор.
/* Chrome 28+ */ @supports (-webkit-appearance:none) { /* Нужные стили */ }
/* Firefox (любой) */ _:-moz-tree-row(hover), .selector { /* Нужные стили */ }
/* Internet Explorer 11+ */ _:-ms-fullscreen, :root .selector { /* Нужные стили */ }
/* Internet Explorer 10+ */ _:-ms-lang(x), .selector { /* Нужные стили */ }
/* Также Internet Explorer 10+ */ @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { /* Нужные стили */ }
/* Internet Explorer 9+ */ _::selection, .selector { /* Нужные стили */ }
/* Safari 6.1+, Chrome для iOS */ @media screen and (min-color-index: 0) and(-webkit-min-device-pixel-ratio: 0) { @media { /* Нужные стили */ }}

не работает socks5 proxy в связке Python3 Selenium3 Phantomjs2

Вот код который пытаюсь выполнить:
from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities import time
ua = dict(DesiredCapabilities.PHANTOMJS) ua["phantomjs.page.settings.userAgent"] = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36") service_args = ['--proxy=109.228.147.75:45554','--proxy-type=socks5','--load-images=false','--ignore-ssl-errors=true'] browser = webdriver.PhantomJS(desired_capabilities=ua, service_args=service_args) browser.set_window_size(1920, 1080) browser.get('http://www.ip-adress.com/') time.sleep(10) browser.save_screenshot('screen_test.png')
Если использовать socks5, то при выполнении кода PhantomJS не может загрузить сайт, оставаясь в подвешенном состоянии, можно ждать вечность и ничего не произойдёт. Если завершить процесс PhantomJS то IDLE прервётся на команде:
browser.get('http://www.ip-adress.com/')
В Мозиле (не в коде, а просто в окне) с этим же proxy сайт открывается.
Если заменить прокси на https тип, то всё заработает.
Однако, через socks5 загружается http://example.com/. То есть только он. Ни один другой сайт не грузится. => Из-за этого подозреваю что проблема с DNS запросами, но как это фиксить?
Python 3.5 / Selenium 3.0.1 / PhantomJS 2.1.1


Ответ

Всё дело в самом proxy. Я так до конца и не понял в чём всё же фишка, но как итог, нужно проверять proxy на работающий DNS Forwarding. Просто работающих соксов много, а вот с адекватным DNS F мне как-то попалось значительно меньше.
Как я понял, PhantomJS при использовании socks5 по умолчанию использует DNS Forwarding, по этому и ждёт, когда-же сервер ему ответит. Однако настройки как в Мозиле:

у Фантома нет, и отключить проксирование DNS запросов у Фантома нельзя.
Хотя я не исключаю, что не прав до сих пор, и реально ответ в другом.
Спасибо))

Обфускация .NET приложений

За счёт каких приемов в .NET достигается то, что нельзя восстановить декомпиляторами исходный код на высокоуровневом языке (языке написания)?
Какие приемы используют современные обфускаторы?
Обратима ли обфускация?


Ответ

Данная пост является почти полной копией статьи https://habrahabr.ru/post/74463/

Также рекомендую обзор обфускаторов для .NET. Несмотря на то, что статья 2010 года, она способна дать понимание, что в основном делают обфускаторы и в чём их недоработки.

Методики обфускации
Объединение сборок и пространств имён (Assembly Merge, Namespace Flatten)
Данная методика сама по себе не задерживает злоумышленника ни на минуту, но очень полезна для дальнейшего его запутывания. Т.к. чем больше классов будет содержать результирующая сборка, тем сложнее без детального анализа будет в ней найти то, что надо. Опять же, при попытке украсть ваш код, злоумышленник получит вместо нескольких проектов-библиотек и одной программы только один проект, в котором все классы будут лежать в одной папке (и в одном неймспейсе).
Для объединения сборок можно использовать утилиту ilmerge, либо встроенную в обфускатор функциональность. Пространства имён обычно объединяются во время обфускации имён классов (чтобы не было коллизий с одинаково названными классами из разных неймспейсов).
Переименование классов, методов и т.д.
Данный подход реализован почти во всех уважающих обфускаторах. Удаляются все «подсказки» которые может использовать злоумышленник для быстрого поиска классов, отвечающих за лицензирование, либо при «воровстве» кода будет очень тяжело разобраться в логике приложения, для чего и как создаются классы, и вызываются методы.
Самый популярный вариант на данный момент — переименование в непечатные символы (или какую-нибудь «китайщину» типа 儽.凍::儽). Это немного осложняет просмотр сборки в рефлекторе, но на деобфускаторе никак не сказывается. К тому же из недостатков мы получаем труднопередаваемые сообщения об исключениях, которые могли бы произойти у конечного пользователя, и если он нам вышлет текст не в юникод-кодировке, то разобрать его будет практически невозможно.

Аналогичный вариант — использование коротких, но печатных идентификаторов (a, b, c, …aa, ab, ac…). Для деобфускатора этот вариант полностью аналогичен предыдущему, но зато лишён указанного недостатка.

Третий вариант именования — использование ключевых слов языка высокого уровня (C# или VB.net), либо невалидных идентификаторов для этого языка (например ?123?) — ничем не лучше двух предыдущих, но почему-то считается, что при «воровстве» кода не воспользуются деобфускатором, и на выходе получится некомпилируемый текст...

Ещё существует куча «глупых» вариантов, которые конечно скрывают смысл исходных имён, но зачем делать их такими длинными?

Интересным и ещё более запутывающим подходом является создание большого количества overload-методов с одним именем, которые имели до обфускации разные имена, и никак не были связаны. Также .net позволяет создавать override-методы, имена которых отличаются от имён методов, которые они перекрыли. Это сбивает с толку не только злоумышленников, но и добавляет лишние требования к деобфускатору.

Изменение содержимого классов
Некоторые обфускаторы могут объединять несколько классов в один, или делать из обычного класса вложенный. Но такая обфускация часто приводит к ошибкам в результирующей программе, и используется очень редко.
Обфускация Control Flow
На этом этапе меняется порядок инструкций в коде и даже меняются сами инструкции. Пожалуй, самый интересный и самый спорный этап. Данная методика позволяет ввести в заблуждение (а иногда и в полный ступор) большинство декомпиляторов языков высокого уровня. Что очень хорошо противодействует «воровству» кода. Также «запутывает» кракеров и авторов кейгенов. Обратная сторона медали — иногда сниженная производительность. Логично, что чем больше мы запутываем ход выполнения программы, тем дольше она выполняется. Особенно это относится к использованию исключений. В большинстве случаев код метода бьётся на блоки, эти блоки перемешиваются в случайном порядке и «склеиваются» с помощью безусловных переходов (инструкции br и br.s). В качестве примера:
L_0034: br.s L_003a L_0036: nop L_0037: br.s L_0041 L_0039: nop L_003a: callvirt instance void [Aaa]Xxx.Yyy::Zzz() L_003f: br.s L_0036 L_0041: nop
Бывают и случаи, когда метод очень короткий, и «перемешать» его хорошо не получается, в этом случае некоторые обфускаторы выдают переход на следующую инструкцию:
L_0008: br.s L_000a L_000a: ldarg.0
Между инструкцией перехода, и её целью очень часто вставляется всякое «фуфло», типа выпадения в дебаггер, или просто невалидных инструкций:
L_0000: br.s L_0003 L_0002: break L_0003: ldarg.0
Некоторые обфускаторы заменяют инструкции перехода (как оригинальные, так и вставленные) на загрузку константы и переход на switch
L_0000: br.s L_0023 L_0002: ldloc num3 L_0006: switch (L_005b, L_0068, L_00ce, L_00af, L_0047, L_007b) ... ... ... L_003c: ldc.i4 4 L_0041: stloc num3 L_0045: br.s L_0002
очевидно, что в данном примере инструкция со смещением L_0045 «в девичестве» была br L_0047, а если учесть предыдущие методики, то это вообще nop ;)
Иногда можно встретить «переход на переход»:

в одной из программ я видел цепочку из 6 (шести) таких переходов ;)
Интересный подход — использование условных переходов для выражений, которые всегда верны (или неверны). Самый простой пример:
L_0014: ldc.i4.1 L_0015: brtrue.s L_002e
То же самое, но слегка более запутанное:
L_0014: ldc.i4.1 L_0015: stloc.0 L_0016: br.s L_001c L_0018: nop L_0019: ldarg.1 L_001a: br.s L_002e L_001c: ldloc.0 L_001d: brtrue.s L_0018
Ещё вариант:
if (5 < (5 - 6)) { // IL-мусор, или неверный код }
в виде IL будет выглядеть примерно так:
L_0000: ldc.i4.5 L_0001: dup L_0002: dup L_0003: ldc.i4.6 L_0004: sub L_0005: blt L_0001
Простое перемешивание некоторых инструкций, например:
L_0000: ldc.i4 4 L_0005: stloc num L_0009: ldstr "\u5f03" L_000e: ldloc num
компилятор иногда может выдавать код вида stloc X, ldloc X, когда требуется записать значение в локальную переменную, но не убирать его со стэка. В случае обфускаторов, эта переменная (num) добавлена искусственно, и нигде кроме данных двух инструкций больше не используется.
Один из самых «жёстких» методов — всегда выбрасываемое внутри блока try—catch исключение. Данный подход используется очень редко, т.к. резко снижает производительность и может нарушить логику приложения при неверном использовании. Скриншот я не привожу т.к. он занимает много места.
Invalid IL
Тут всё очень просто. В участки кода, которые никогда не будут исполнены, вставляются не описанные в стандарте опкоды (т.е. невалидные инструкции). В рефлекторе вы увидите примерно такое:

или, если переключится на IL:

Данная методика обескураживает начинающих «хаксоров». Но не является чем-то сложным для обхода (данные опкоды просто заменяются на nop).
Сокрытие строк
Деобфускаторами это называется «шифрование строк», но назвать это шифрованием у меня не поворачивается язык. Обычно это делается каким-нибудь «детским» алгоритмом шифрования типа XOR на константу:
public static string Decode(string str, int num) { int length = str.Length; char[] chArray = str.ToCharArray(); while (--length >= 0) chArray[length] = (char)(chArray[length] ^ num); return new string(chArray); }
Иногда строки объединяются в одну, и потом происходит вызов метода Substring; иногда строки прячут в ресурсы.
В любом случае «шифрование» представлено в виде статического метода с несколькими аргументами, обычно это строка и/или число. Никаких криптографических алгоритмов не применяется, что вполне логично: если применить здесь настоящее шифрование, то программа будет безбожно тормозить. Данный метод спасает от начинающих кракеров, которые будут искать по коду строки типа “Invalid serial number” или другие тексты сообщений.
Специфичные атрибуты и баги декомпиляторов
Самый часто встречаемый атрибут — [SuppressIldasm], который «вежливо просит» не работать на данной сборке официальный декомпилятор Microsoft — ildasm. Существуют также специфичные атрибуты для рефлектора и для коммерческих декомпиляторов.
В качестве багов можно встретить как чисто технические недоработки декомпиляторов (например, рефлектор выпадает на инструкции ldfld string 儽.凍::儽, а большинство деобфускаторов на базе Mono.Cecil — на неправильных RVA), также и алгоритмичиские допущения: многие декомпиляторы высокого уровня отслеживают состояние стэка, но идут по методу не как по графу, а линейно, и радостно валятся на методах в которых после последней инструкции ret вставлен бесконечный цикл. Против плагина Reflexil хорошо «помогает» инструкция, переходящая сама на себя.
Другие методы
Иногда можно встретить очень похожий на скрытие строк подход, но для ресурсов. Также один из обфускаторов предлагает «V-Spot Elimination» (чем очень гордится) — создание прокси-классов для классов BCL, что замедляет анализ и слегка портит полученный декомпиляцией код. Также используется конвертирование managed в unmanaged .net-кода. Т.е. все пересобирается с пометками unmanaged. Практически вся функциональность в пределах домена сохраняется, но рефлектором код уже не посмотреть.

Фильтрация данных ListView при использовании CursorLoader

Есть активность с AutoCompleteTextView и ListView. В ListView данные выводятся через свой адаптер.
Как сделать так чтобы при выполнении метода onTextChanged у AutoCompleteTextView данные в ListView менялись следующим образом: А именно чтобы в ListView оставались только те слова, которые начинаются на тот набор символов, которые вводим в AutoCompleteTextView? Я понимаю что нужно каждый раз обновлять содержимое курсора, но не представляю как.
Код DBHelper
public class DBHeler extends SQLiteOpenHelper { private static String DB_PATH = "/data/data/tests.mytest3/databases/"; private static final String DATABASE_NAME = "dbase.db"; private static final int DATABASE_VERSION = 1; public SQLiteDatabase database; private Context myContext;
final String ruQuery = "SELECT * " + " FROM " + Contract.Entry.TABLE_RUEN; final String enQuery = "SELECT * " + " FROM " + Contract.Entry.TABLE_ENRU;
public DBHeler(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); this.myContext = context; }
public Cursor getRuWords() { return database.rawQuery(ruQuery, null); }
public Cursor getEnWords() { return database.rawQuery(enQuery, null); }
public void createDataBase() throws IOException { boolean dbExist = checkDataBase();
if(dbExist){ //ничего не делать - база уже есть }else{ //вызывая этот метод создаем пустую базу, позже она будет перезаписана this.getReadableDatabase();
try { copyDataBase(); } catch (IOException e) { throw new Error("Error copying database"); } } }
private boolean checkDataBase(){ SQLiteDatabase checkDB = null; try { String myPath = DB_PATH + DATABASE_NAME; checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY); } catch(SQLiteException e){ //база еще не существует } if(checkDB != null){ checkDB.close(); } return checkDB != null ? true : false; }
private void copyDataBase() throws IOException{ InputStream myInput = myContext.getAssets().open(DATABASE_NAME);
String outFileName = DB_PATH + DATABASE_NAME;
OutputStream myOutput = new FileOutputStream(outFileName);
byte[] buffer = new byte[1024]; int length; while ((length = myInput.read(buffer))>0){ myOutput.write(buffer, 0, length); }
myOutput.flush(); myOutput.close(); myInput.close(); }
public void openDataBase() throws SQLException { String myPath = DB_PATH + DATABASE_NAME; database = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE); }
@Override public synchronized void close() { if(database != null) database.close(); super.close(); }
@Override public void onCreate(SQLiteDatabase db) { }
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
} }
Класс Contract
public class Contract { private Contract() { };
public static final class Entry implements BaseColumns { public final static String TABLE_RUEN = "ruen"; public final static String TABLE_ENRU = "enru";
public final static String _ID = BaseColumns._ID; public final static String COLUMN_SLOVO = "word"; public final static String COLUMN_PEREVOD = "translation"; public final static String COLUMN_IZBRANNOE = "favorites"; } }
Класс адаптера
public class MySimpleCursorAdapter extends SimpleCursorAdapter {
private int layout;
public MySimpleCursorAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to, int flag) { super(context, layout, cursor, from, to, flag); this.layout = layout; }
public static class ViewHolder { public TextView txtBukva; public TextView txtPerevod; public TextView txtSlovo; public ImageButton btnIzbrannoe;
public ViewHolder(View view) { txtBukva = (TextView) view.findViewById(R.id.txtBukva); txtSlovo = (TextView) view.findViewById(R.id.txtSlovo); txtPerevod = (TextView) view.findViewById(R.id.txtPerevod); btnIzbrannoe = (ImageButton) view.findViewById(R.id.btnIzbrannoe); } }
@Override public View newView(Context context, Cursor cursor, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(layout, parent, false); ViewHolder viewHolder = new ViewHolder(view); view.setTag(viewHolder); return view; }
@Override public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
String bukva = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_SLOVO)).substring(0, 1).toUpperCase(); String slovo = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_SLOVO)); String perevod = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_PEREVOD)); String izbrannoe = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_IZBRANNOE)); int _ID = cursor.getInt(cursor.getColumnIndex(Contract.Entry._ID));
TextView txtBukva = (TextView) view.findViewById(R.id.txtBukva); TextView txtSlovo = (TextView) view.findViewById(R.id.txtSlovo); TextView txtPerevod = (TextView) view.findViewById(R.id.txtPerevod); ImageButton btnIzbrannoe = (ImageButton) view.findViewById(R.id.btnIzbrannoe);
holder.txtBukva.setText(bukva); holder.txtSlovo.setText(slovo); holder.txtPerevod.setText(perevod); holder.btnIzbrannoe.setFocusable(false);
if (izbrannoe.equals("1")) { holder.btnIzbrannoe.setImageResource(R.drawable.icon_star_yellow); } else { holder.btnIzbrannoe.setImageResource(R.drawable.icon_star_outline_black); }
holder.btnIzbrannoe.setTag(new Slovo(_ID,slovo, perevod,bukva,izbrannoe)); } }
MainActivity
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {
private EditText txtSearch; private Spinner spinner; private ListView list; private ImageButton btnClear;
DBHeler db; private MySimpleCursorAdapter cursorAdapter;
final private static int LOADER_RUS = 0; final private static int LOADER_ENG = 1;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
db = new DBHeler(this);
try { db.createDataBase(); db.openDataBase(); } catch (IOException ex) { ex.printStackTrace(); }
spinner = (Spinner) findViewById(R.id.spinner); txtSearch = (EditText) findViewById(R.id.txtSearch); list = (ListView) findViewById(R.id.list); btnClear = (ImageButton) findViewById(R.id.btnClear);
ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.types, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter);
String[] from = new String[] {Contract.Entry.COLUMN_SLOVO, Contract.Entry.COLUMN_SLOVO, Contract.Entry.COLUMN_PEREVOD, Contract.Entry.COLUMN_IZBRANNOE}; int[] to = new int[] {R.id.txtBukva, R.id.txtSlovo, R.id.txtPerevod, R.id.btnIzbrannoe};
cursorAdapter = new MySimpleCursorAdapter(this, R.layout.item, null, from, to, 0);
list.setAdapter(cursorAdapter);
// инициализируем оба загрузчика getSupportLoaderManager().initLoader(LOADER_RUS, null, this); getSupportLoaderManager().initLoader(LOADER_ENG, null, this);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView adapterView, View view, int loadID, long l) { getSupportLoaderManager().getLoader(loadID).forceLoad(); // обновляем данные в курсоре } @Override public void onNothingSelected(AdapterView adapterView) { } }); }
@Override protected void onResume() { super.onResume();
list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView adapterView, View view, int i, long l) { Intent intent = new Intent(MainActivity.this, SlovoActivity.class); CharSequence strCharSequence = ((TextView)view.findViewById(R.id.txtSlovo)).getText(); String str = strCharSequence.toString().toLowerCase().trim(); String selectedItem = spinner.getSelectedItem().toString(); if (selectedItem.equals("С русского на английский")) { intent.putExtra("slovo", str); intent.putExtra("type", "RU"); startActivity(intent); } else if (selectedItem.equals("С английского на русский")) { intent.putExtra("slovo", str); intent.putExtra("type", "EN"); startActivity(intent); } } }); }
public void onFavoriteClick(View view) {
Slovo data = (Slovo)view.getTag(); long id = data.id; String slovo = data.word; String perevod = data.perevod; String bukva = data.simbol; boolean izbrannoe = data.favorite;
String selectedItem = spinner.getSelectedItem().toString(); if (selectedItem.equals("С русского на английский")) { if (!izbrannoe) { ContentValues values = new ContentValues(); values.put(Contract.Entry.COLUMN_IZBRANNOE, "1");
// Вставляем новый ряд в базу данных и запоминаем его идентификатор long newRowId = db.database.update(Contract.Entry.TABLE_RUEN, values, Contract.Entry.COLUMN_SLOVO + "= ?", new String[]{slovo}); //izbrannoe = true; // Выводим сообщение в успешном случае или при ошибке if (newRowId == -1) { // Если ID -1, значит произошла ошибка Toast.makeText(this, "Ошибка", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Добавлено в избранное", Toast.LENGTH_SHORT).show(); getSupportLoaderManager().getLoader(LOADER_RUS).forceLoad(); } } else if (izbrannoe) { ContentValues values = new ContentValues(); values.put(Contract.Entry.COLUMN_IZBRANNOE, "0");
// Вставляем новый ряд в базу данных и запоминаем его идентификатор long newRowId = db.database.update(Contract.Entry.TABLE_RUEN, values, Contract.Entry.COLUMN_SLOVO + "= ?", new String[]{slovo}); //izbrannoe = true; // Выводим сообщение в успешном случае или при ошибке if (newRowId == -1) { // Если ID -1, значит произошла ошибка Toast.makeText(this, "Ошибка", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Удалено из избранного", Toast.LENGTH_SHORT).show(); getSupportLoaderManager().getLoader(LOADER_RUS).forceLoad(); } } } else if (selectedItem.equals("С английского на русский")) { if (!izbrannoe) { ContentValues values = new ContentValues(); values.put(Contract.Entry.COLUMN_IZBRANNOE, "1");
// Вставляем новый ряд в базу данных и запоминаем его идентификатор long newRowId = db.database.update(Contract.Entry.TABLE_ENRU, values, Contract.Entry.COLUMN_SLOVO + "= ?", new String[]{slovo}); //izbrannoe = true; // Выводим сообщение в успешном случае или при ошибке if (newRowId == -1) { // Если ID -1, значит произошла ошибка Toast.makeText(this, "Ошибка", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Добавлено в избранное", Toast.LENGTH_SHORT).show(); getSupportLoaderManager().getLoader(LOADER_ENG).forceLoad(); } } else if (izbrannoe) { ContentValues values = new ContentValues(); values.put(Contract.Entry.COLUMN_IZBRANNOE, "0");
// Вставляем новый ряд в базу данных и запоминаем его идентификатор long newRowId = db.database.update(Contract.Entry.TABLE_ENRU, values, Contract.Entry.COLUMN_SLOVO + "= ?", new String[]{slovo}); //izbrannoe = true; // Выводим сообщение в успешном случае или при ошибке if (newRowId == -1) { // Если ID -1, значит произошла ошибка Toast.makeText(this, "Ошибка", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Удалено из избранного", Toast.LENGTH_SHORT).show(); getSupportLoaderManager().getLoader(LOADER_ENG).forceLoad(); } } } }
protected void onDestroy() { super.onDestroy(); db.close(); }
static class MyCursorLoader extends CursorLoader { Cursor cursor; DBHeler dbHeler; final int loaderID;
public MyCursorLoader(Context context, DBHeler dbHeler, int id) { super(context); this.dbHeler = dbHeler; loaderID = id; }
@Override protected Cursor onLoadInBackground() { switch (loaderID) { case LOADER_RUS: cursor = dbHeler.getRuWords(); break; case LOADER_ENG: cursor = dbHeler.getEnWords(); break; } return cursor; } }
@Override public Loader onCreateLoader(int id, Bundle args) { return new MyCursorLoader(this, db, id); }
@Override public void onLoadFinished(Loader loader, Cursor data) { cursorAdapter.swapCursor(data); }
@Override public void onLoaderReset(Loader loader) { cursorAdapter.swapCursor(null); } }
Класс Slovo
public class Slovo { long id; String word; String perevod; String simbol; boolean favorite;
public Slovo(long id, String word, String perevod, String simbol, String favorite) { this.id = id; this.word = word; this.perevod = perevod; this.simbol = simbol; this.favorite = (favorite.equals("1"))? true:false; } }


Ответ

Все в связке выглядит примерно так. Код даю с импортами, потому что есть дублирующие классы из API и библиотек поддержки - чтобы не было ошибок.
Для реализации фильтра по данным списка, управляемого через CursorLoader нужно проделать всего две вещи:
Получить строку для фильтрации Составить запрос в БД на получение данных по этой строке Уведомить загрузчик о изменениях
Строку для фильтра можно вводить в EditText или SearchView. Подключаем слушатель, который позволяет получать текст по мере набора и делаем в БД запросы по этому набору. Для обновления списка используем метод менеджера загрузчиков restartLoader(), который уведомляет, что выборка из БД изменилась и надо обновить список.
Запрос на выборку данных из БД по неполному соответствию (только начало слова) делаем через оператор SQL LIKE, который находит вхождение подстроки в строке. На SQL запрос выглядит так:
SELECT * FROM table WHERE UPPER(column) LIKE substrig%
что значит - выбрать все столбцы из таблицы table, где в столбце column встречается подстрока substring без учета регистра (с кириллическими символами к сожалению без мутных костылей это не работает).
класс MainActivity. Здесь помимо реализации самого фильтра по списку много рефакторинга, улучшающего и оптимизирующего код, надеюсь вы сравните и примите на заметку в будущем:
import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v4.content.CursorLoader; import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ListView; import android.widget.Spinner; import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {
private EditText txtSearch; private Spinner spinner; private ListView list; private ImageButton btnClear;
DBHeler db; private MyCursorAdapter cursorAdapter; private int currentLoader; final private static int LOADER_RUS = 0; final private static int LOADER_ENG = 1;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
db = new DBHeler(this);
spinner = (Spinner) findViewById(R.id.spinner); txtSearch = (EditText) findViewById(R.id.txtSearch); list = (ListView) findViewById(R.id.list); btnClear = (ImageButton) findViewById(R.id.btnClear);
ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.types, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter);
cursorAdapter = new MyCursorAdapter(this, R.layout.item, null, 0); list.setAdapter(cursorAdapter);
// при старте выводим все данные (фильтр - пустая строка) Bundle bundle = new Bundle(1); bundle.putString("filter", "");
getSupportLoaderManager().initLoader(LOADER_RUS, bundle, this); getSupportLoaderManager().initLoader(LOADER_ENG, bundle, this);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView adapterView, View view, int loadID, long l) { getSupportLoaderManager().getLoader(loadID).forceLoad(); currentLoader = loadID; txtSearch.setText(null); // при смене языка очищаем поле поиска } @Override public void onNothingSelected(AdapterView adapterView) { } });
txtSearch.addTextChangedListener(new TextWatcher(){ public void onTextChanged(CharSequence s, int start, int before, int count) { // получаем строку по мере ввода refreshCursor(s.toString()); } @Override public void afterTextChanged(Editable text) {} @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} }); }
private void refreshCursor(String str) { //передаем в загрузчик строку для фильтра и перезапускаем загрузчик для обновления списка Bundle bundle = new Bundle(1); bundle.putString("filter", str); getSupportLoaderManager().restartLoader(currentLoader, bundle, this); }
@Override protected void onResume() { super.onResume();
list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView adapterView, View view, int position, long l) { Intent intent = new Intent(MainActivity.this, SlovoActivity.class); Cursor cursor = (Cursor) cursorAdapter.getItem(position); String str = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_SLOVO)).toLowerCase().trim(); int selectedItem = spinner.getSelectedItemPosition(); intent.putExtra("slovo", str); intent.putExtra("type", selectedItem == 0 ? "RU" : "EN"); startActivity(intent); } }); }
public void onFavoriteClick(View view) {
Slovo data = (Slovo)view.getTag(); long id = data.id; String slovo = data.word; String perevod = data.perevod; String bukva = data.simbol; boolean izbrannoe = data.favorite;
String table; String fav; String toast; int currentLoader;
if (spinner.getSelectedItemPosition() == 0) { table = Contract.Entry.TABLE_RUEN; currentLoader = LOADER_RUS; } else { table = Contract.Entry.TABLE_ENRU; currentLoader = LOADER_ENG; }
if (izbrannoe) { fav = "0"; toast = "Удалено из избранного"; } else { fav = "1"; toast = "Добавлено в избранное"; }
ContentValues values = new ContentValues(); values.put(Contract.Entry.COLUMN_IZBRANNOE, fav); long newRowId = db.database.update(table, values, Contract.Entry.COLUMN_SLOVO + "= ?", new String[]{slovo}); if (newRowId == -1) { Toast.makeText(this, "Ошибка", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, toast, Toast.LENGTH_SHORT).show(); getSupportLoaderManager().getLoader(currentLoader).forceLoad(); } }
protected void onDestroy() { super.onDestroy(); // закрывать БД здесь не следует, так как это приводит к падениям при повороте // многие склоняются к мысли, что БД вообще не следует закрывать }
@Override public Loader onCreateLoader(int id, Bundle bundle) { String filter = bundle.getString("filter"); return new MyCursorLoader(this, db, id, filter); }
@Override public void onLoadFinished(Loader loader, Cursor data) { cursorAdapter.swapCursor(data); }
@Override public void onLoaderReset(Loader loader) { cursorAdapter.swapCursor(null); }
static class MyCursorLoader extends CursorLoader { Cursor cursor; DBHeler dbHeler; final int loaderID; String filter;
public MyCursorLoader(Context context, DBHeler dbHeler, int id, String filter) { super(context); this.dbHeler = dbHeler; loaderID = id; this.filter = filter; }
@Override protected Cursor onLoadInBackground() { switch (loaderID) { case LOADER_RUS: // выборка с учетом фильтра cursor = dbHeler.getRuWords(filter); break; case LOADER_ENG: cursor = dbHeler.getEnWords(filter); break; } return cursor; } } }
Информация о необходимости [не]закрытия базы данных.
Класс MyCursorAdapter. Здесь тоже небольшой рефакторинг и оценив, что требуется от адаптера в вашем случае, решил отказаться от SimpleCursorAdapter, так как его возможности все равно не используются:
import android.content.Context; import android.database.Cursor; import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.TextView;
public class MyCursorAdapter extends CursorAdapter {
private int layout;
public MyCursorAdapter(Context context, int layout, Cursor cursor, int flag) { super(context, cursor, flag); this.layout = layout; }
public static class ViewHolder { public TextView txtBukva; public TextView txtPerevod; public TextView txtSlovo; public ImageButton btnIzbrannoe;
public ViewHolder(View view) { txtBukva = (TextView) view.findViewById(R.id.txtBukva); txtSlovo = (TextView) view.findViewById(R.id.txtSlovo); txtPerevod = (TextView) view.findViewById(R.id.txtPerevod); btnIzbrannoe = (ImageButton) view.findViewById(R.id.btnIzbrannoe); } }
@Override public View newView(Context context, Cursor cursor, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(layout, parent, false); ViewHolder viewHolder = new ViewHolder(view); view.setTag(viewHolder); return view; }
@Override public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
String bukva = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_SLOVO)).substring(0, 1).toUpperCase(); String slovo = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_SLOVO)); String perevod = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_PEREVOD)); String izbrannoe = cursor.getString(cursor.getColumnIndex(Contract.Entry.COLUMN_IZBRANNOE)); int _ID = cursor.getInt(cursor.getColumnIndex(Contract.Entry._ID));
holder.txtBukva.setText(bukva); holder.txtSlovo.setText(slovo); holder.txtPerevod.setText(perevod); holder.btnIzbrannoe.setFocusable(false);
// здесь получение ссылок на виджеты совершенно излишне и "убивает" // преимущество использования ViewHolder
if (izbrannoe.equals("1")) { holder.btnIzbrannoe.setImageResource(R.drawable.icon_star_yellow); } else { holder.btnIzbrannoe.setImageResource(R.drawable.icon_star_outline_black); }
holder.btnIzbrannoe.setTag(new Slovo(_ID,slovo, perevod,bukva,izbrannoe)); } }
класс DBhelper. Ваш вариант этого класса не захотел у меня копировать БД, предлагаю свой, не зависимый от имени приложения. Так же небольшой рефакторинг - в БД лучше отправлять не "сырые" запросы rawQuery()), а через метод query(), он более безопасен:
public class DBHeler extends SQLiteOpenHelper { private static String DB_PATH; private static final String DATABASE_NAME = "dbase.db"; private static final int DATABASE_VERSION = 1; public SQLiteDatabase database; private Context myContext;
public DBHeler(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); this.myContext = context; try { //получаем путь до БД вместе с именем. DB_PATH = myContext.getDatabasePath(DATABASE_NAME).toString(); createDataBase(); openDataBase(); } catch (IOException e) { e.printStackTrace(); } }
public Cursor getRuWords(String filter) { return database.query(Contract.Entry.TABLE_RUEN, new String[] {"*"}, "UPPER(" + Contract.Entry.COLUMN_SLOVO + ")" + " LIKE ?", new String[] {filter + "%"}, null, null, null); }
public Cursor getEnWords(String filter) { return database.query(Contract.Entry.TABLE_ENRU, new String[] {"*"}, "UPPER(" + Contract.Entry.COLUMN_SLOVO + ")" + " LIKE ?", new String[] {filter + "%"}, null, null, null); }
public void createDataBase() throws IOException {
boolean dbExist = checkDataBase();
if (!dbExist) { this.getReadableDatabase(); try { copyDataBase(); } catch (IOException e) { throw new Error("Error copying database"); } } }
private boolean checkDataBase(){ SQLiteDatabase checkDB = null; try {
checkDB = SQLiteDatabase.openDatabase(DB_PATH, null, SQLiteDatabase.OPEN_READONLY); } catch (SQLiteException e) { // база не существует } if (checkDB != null) { checkDB.close(); } return checkDB != null; }
private void copyDataBase() throws IOException { InputStream myInput = myContext.getAssets().open(DATABASE_NAME);
String outFileName = DB_PATH;
OutputStream myOutput = new FileOutputStream(outFileName);
byte[] buffer = new byte[1024]; int length; while ((length = myInput.read(buffer))>0){ myOutput.write(buffer, 0, length); }
myOutput.flush(); myOutput.close(); myInput.close(); }
public void openDataBase() throws SQLException { database = SQLiteDatabase.openDatabase(DB_PATH, null, SQLiteDatabase.OPEN_READWRITE); }
@Override public synchronized void close() { if(database != null) database.close(); super.close(); }
@Override public void onCreate(SQLiteDatabase db) { }
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
} }
Остальные классы без измений. Данные классы лучше копировать целиком, так как вносил много изменений в разных местах.