Данный проект направлен на реализацию возможности разработки Android-приложений таким же образом, как и веб-приложений. После того, как код будет написан и опубликован, пользователи смогут сразу его видеть, а не должны будут каждый раз собирать apk, обновлять и устанавливать. Реализация осуществляется путём динамической замены Activity и ресурсных файлов.
27 декабря 2019 года 14:58:43 Проект перешёл к использованию методов hotfix, применяя метод замены родительского ClassLoader вместо предыдущего рефлексивного замены классов.
Как известно, в Android обычно имеются два ClassLoader: BootCLassLoader и PathClassLoader.
При загрузке класса используется принцип двойной проверки, то есть сначала BootCLassLoader пытается загрузить данный класс, если он отсутствует, то загружает PathClassLoader.
Основной метод замены данного проекта заключается в том, чтобы между BootCLassLoader и PathClassLoader вставить пользовательский Wandaloader. Таким образом, когда наш проект загружает класс, он сначала пытается загрузить его через Wandaloader, что позволяет нам достичь цели замены без использования рефлексии.
Пример демонстрационного эффекта данного проекта: (Обновление 12 апреля 2019 года 17:23:29: Для использования этого проекта в реальных проектах, я создал простой проект, который может быть хорошим примером использования данного проекта. Перейти)
Условия использования:
Преимущества и недостатки:
В файл build.gradle проекта добавьте:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Затем добавьте зависимости в :app:
annotationProcessor 'com.github.miqt.WandFix:wand-compiler:v1.3.6'
implementation 'com.github.miqt.WandFix:wand:v1.3.6'
Динамическое прокси для активити — это когда все операции активити выполняются через прокси-класс. Этот прокси-класс сам представляет собой объект, поэтому мы можем использовать DexClassLoader для замены этого объекта, что позволяет реализовать динамический прокси.
Создайте новый класс активити, который наследуется от ProxyActivity, и добавьте аннотацию @BindProxy
.
// Бinds the proxy class
@BindProxy(clazz = TextActivityProxy.class)
// Must inherit from ProxyActivity
public class TextActivity extends ProxyActivity {
// Здесь ничего не нужно писать
}
Затем создайте новый класс, который наследуется от ActivityProxy, и реализуйте методы прокси.
// Каждый прокси-класс должен быть добавлен
@AddToFixPatch
public class TextActivityProxy extends ActivityProxy {
public TextActivityProxy(ProxyActivity acty) {
super(acty);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
mActy.setContentView(R.layout.activity_hello);
...
}
...
}
Запустите этот активити с помощью startActivity(new Intent(this, TextActivity.class));
. Это позволит включить прокси. В будущем, если потребуется изменить прокси-класс, достаточно собрать пакет с горячими исправлениями и отправить его.
public class MainActivity extends AppCompatActivity implements Wand.MotorListener {
``` ```java
@InjectObject(
// full class name
value = "com.miqt.demo.presenter.AppPresenterImpl",
// setting parental trust
// during development it is recommended to use PROJECT, prioritizing the use of local libraries.
// upon release of the project, it should be changed to NEVER, prioritizing the use of hotfix package libraries.
level = ParentalEntrustmentLevel.NEVER)
AppPresenter ap;
``` @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// можно инициализировать один раз в application
Wand.get().init(this).listener(this);
}
public void getStr(View view) {
// использование аннотации для внедрения объекта требует вызова этой строки
ClassInstall.inject(this);
// или конструктора с параметрами
// Map<String, Object[]> paramHouse = new HashMap<>();
// paramHouse.put("com.miqt.demo.presenter.AppPresenterImpl", new Object[] {"hello"});
// ClassInstall.inject(this, paramHouse);
// также можно инициализировать объект без использования аннотации для внедрения
// ap = ObjectFactory.make("com.miqt.demo.presenter.AppPresenterImpl" /*, параметры конструктора */);
// ap = ObjectFactory.make(AppPresenterImpl.class /*, параметры конструктора */);
String str = ap.getStr();
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
}
// сначала создайте PluginResources по пути к файлу плагина
// требуется разрешение на чтение/запись файлов
PluginResources manager = new PluginResources(context, pluginApkPath);
// загрузка цветовых ресурсов
int id = manager.getId("text_color", "color");
int color = manager.getResources().getColor(id, context.getTheme());
textView.setTextColor(color);
// загрузка макета
View main_layout = manager.getLayout("activity_main");
```### Описание использования связанных аннотаций и их функций
1. @InjectObject добавьте к членам переменной для внедрения объекта в эту переменную.
2. @AddToFixPatch добавьте к классу без параметров, чтобы указать, что этот класс связан с горячими исправлениями. В процессе компиляции и сборки пакета будут добавлены классы, помеченные этим аннотированием, а также классы, помеченные аннотацией @InjectObject.
3. @BindProxy добавьте к активностям для привязки специального прокси.### Настройка ProGuard
Если проект использует ProGuard, скопируйте следующий код в конфигурационный файл ProGuard:
-keep class com.miqt.wand.** {;} -keep class * {@com.miqt.wand.anno.InjectObject ; } -keep @com.miqt.wand.anno.AddToFixPatch class * {;} -keep @com.miqt.wand.anno.BindProxy class * {*;}
## Другое
Долгий путь начинается с первого шага, приветствуем ваши вопросы и предложения.
[Как создать и применить пакет горячих исправлений](https://github.com/miqt/WandFix/wiki/Как-создать-и-применить-пакет-горячих-исправлений)
Для получения более подробной информации перейдите на страницу [Wiki](https://github.com/miqt/WandFix/wiki).
Если вам понравился данный проект, то нажмите кнопку "Star" ( ̄▽ ̄)*
## Проблемы, возникшие во время разработки и существующие проблемы- [x] При `targetVersion` > 25 активность-прокси не работает (решено)
- [x] При наличии анонимных внутренних классов в пакете горячих исправлений возникает ошибка `java.lang.NoClassDefFoundError` (решено)
- [x] При использовании `findViewById` в активности-прокси иногда контролы не находятся или возникают ошибки преобразования типов (решено)
- [ ] Обработка ProGuard пока недостаточно хороша, поскольку из-за использования рефлексии при активации ProGuard могут возникнуть ошибки `ClassNotFoundException`, но если не использовать ProGuard, защита исходного кода будет недостаточной. Однако у меня есть несколько идей решения этой проблемы, которые еще не были реализованы.
- [ ] Первый вариант: анализ файла `.app.build.outputs.mapping.debug.mapping.txt` для получения отображения между исходными классами, методами и именами и их зашифрованными версиями, затем поиск соответствующего класса в коде.
- [ ] Второй вариант: использование шифрования dex, где пакет горячих исправлений фактически представляет собой файл dex, который можно зашифровать с помощью пользовательского алгоритма, тем самым ограничивая возможность декомпиляции.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )