Этот проект направлен на реализацию разработки Android, подобной веб-разработке, где код, написанный и опубликованный, позволяет пользователям сразу увидеть изменения, а не требует каждый раз сборки apk -> обновления -> установки. Метод реализации заключается в динамической замене Activity и ресурсных файлов.
27 декабря 2019 года 14:58:43 Этот проект изменил методы горячей фиксации, заменив предыдущий метод замены классов с помощью рефлексии на использование Hack ClassLoader mParent.
Как известно, в Android обычно имеются два ClassLoader: BootClassLoader и PathClassLoader.
При загрузке класса, на основе принципа двойной проверки, сначала BootClassLoader пытается загрузить этот класс, если он не найден, то загрузка происходит с помощью PathClassLoader.
Основной метод замены проекта заключается в том, чтобы вставить между BootClassLoader и PathClassLoader пользовательский WandClassLoader. Таким образом, когда проект загружает класс, он сначала пытается загрузить его с помощью WandClassLoader, что позволяет достичь цели замены без использования рефлексии.
Пример работы проекта: (обновлено 12 апреля 2019 года 17:23:29: для использования этого проекта в реальных проектах, я также написал простой проект, который может быть хорошим примером использования этого проекта. Перейти)
Преимущества:
В файл build.gradle проекта добавьте:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Затем добавьте зависимости в файл build.gradle модуля app:
annotationProcessor 'com.github.miqt.WandFix:wand-compiler:v1.3.6'
implementation 'com.github.miqt.WandFix:wand:v1.3.6'
Динамическое прокси для Activity означает, что все действия одного Activity передаются в прокси-класс для обработки. Прокси-класс по сути является обычным классом, объектом, поэтому мы можем использовать DexClassloader для замены, что и реализует динамическое прокси.
Сначала создайте новый Activity, унаследованный от ProxyActivity, и добавьте аннотацию @BindProxy
// Привязка прокси-класса
@BindProxy(clazz = TextActivityProxy.class)
// Обязательно унаследован от 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);
...
}
...
}
Просто запустите этот Activity с помощью startActivity(new Intent(this, TextActivity.class));
, и прокси начнет работать. Впоследствии, если изменится прокси-класс, можно отправить изменения через сборку пакета для горячей перезагрузки.
public class MainActivity extends AppCompatActivity implements Wand.MotorListener {
@InjectObject(
// полное имя класса
value = "com.miqt.demo.presenter.AppPresenterImpl",
// установка родительского доверия
// во время разработки рекомендуется использовать PROJECT, чтобы приоритет был у локальных классов.
// при выпуске проекта следует изменить на NEVER, чтобы приоритет был у классов из пакета горячей перезагрузки.
level = ParentalEntrustmentLevel.NEVER)
AppPresenter ap;```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// можно инициализировать один раз в приложении
Wand.get().init(this).listener(this);
}
public void getStr(View view) {
// для использования аннотации для вставки объекта, необходимо вызвать эту строку
ClassInstall.inject(this);
// или конструктор с параметрами
// Map<String, Object[]> pramHouse = new HashMap<>();
// pramHouse.put("com.miqt.demo.presenter.AppPresenterImpl", new Object[]{"hello"});
// ClassInstall.inject(this, pramHouse);
// также можно инициализировать объект без использования аннотации
// 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 по пути к файлу плагина APK
// требуется разрешение на чтение и запись файлов
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");
// сначала создаем PluginResources по пути к файлу плагина APK
// требуется разрешение на чтение и запись файлов
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 добавьте к классам активности для привязки к определённому агенту активности.### Настройка кода для обфускации
Если проект настроен на обфускацию, необходимо скопировать следующий код в файл конфигурации обфускации:
-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)
Если вам понравился этот проект, оставьте звёздочку ( ̄▽ ̄)~*
## Проблемы, возникшие во время разработки и существующие проблемы- [x] При targetVersion > 25 активность-агент не работает (решено)
- [x] При наличии анонимных внутренних классов в пакете горячих исправлений возникает ошибка java.lang.NoClassDefFoundError (решено)
- [x] При использовании findViewById в активности-агенте иногда контролы не находятся или возникают ошибки преобразования типов (решено)
- [ ] Обработка обфускации пока не на высоте, из-за использования рефлексии, если код обфусцирован, возникает ошибка ClassNotFoundException, а если не обфусцирован, то защита исходного кода не гарантируется. Однако у меня есть несколько идей по решению этой проблемы, которые ещё не были реализованы.
- [ ] Идея 1: С помощью анализа файла .\app\build\outputs\mapping\debug\mapping.txt получить отображение между исходными классами, методами и именами и их обфусцированными версиями, и использовать это для поиска соответствующих классов в коде.
- [ ] Идея 2: Реализовать оболочку для dex, так как пакет горячих исправлений фактически представляет собой файл dex, а использование пользовательской оболочки может частично предотвратить декомпиляцию.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )