Skip to content

Latest commit

 

History

History
483 lines (310 loc) · 43.7 KB

performance.md

File metadata and controls

483 lines (310 loc) · 43.7 KB
id title
performance
Ажиллагаа

WebView дээр суурилсан аргууд байхад React Native ашиглах гол шалтгаан бол нэг секундэд 60 фрейм гаргаж, натив харагдаж, мэдрэгдэх апп хийж болдогт оршино.Бид та бүхнийг React Native-ыг ашиглан зөв зүйлээ хийгээд, ажиллагааг нь оновчтой болгох гэж биш аппдаа гол анхаарч ажиллахад тань туслахын төлөө хичээх болно. Гэхдээ бидний хараахан хөгжүүлж чадаагүй бас бус зүйлс бий. React Native (шууд натив код бичихийн нэгэн адил) танд зориулж хамгийн оновчтой замыг зааж чадахгүй. Тиймдээ ч өөрөө хийж, тохируулахаас өөр аргагүй. Бид хэрэглэгч ашиглахад өөгүй сайхан ажиллагаатай байхаар анхнаасаа тохиргоотой байлгахын төлөө хичээдэг ч заримдаа энэ нь боломжгүй байх үе бий.

Ажиллагаатай холбоотой асуудлыг шийдэх тухай үндсэн ухагдахуун, мөн асуудлын түгээмэл эх үүсвэр хийгээд зохих шийдлийн тухай уншаарай.

Фреймын тухай та юу мэдэх шаардлагатай вэ?

Эмээ, өвөө чинь киног "хөдөлгөөнт зураг" гэж нэрлэдэг байсан нь учиртай. Видео бол бодит үнэндээ хөдөлгөөнгүй зургыг тогтмол хурдаар маш хурдан өөрчилж байгаа юм. Энэ зургуудыг фреймууд гэж ойлгох хэрэгтэй. Секунд бүрт хичнээн фрейм дэлгэц дээр гаргах нь тухайн видео нь өв тэгш, амьд болж чадах эсэхэд нөлөөлдөг. iOS төхөөрөмж секундэд 60 фрейм харуулж чадах ба та болон хэрэглэгчийн интерфэйс 16.67 миллисекундэд тухайн хөдөлгөөнгүй зураг (фрейм) гарахад шаардлагатай бүх ажлыг хийж байж түүнийг чинь хэрэглэгч дэлгэц дээр харна. Хэрэв өгөгдсөн 16.67 миллисекундэд тухайн фреймыг боловсруулах ажлыг хийж чадахгүй бол "drop a frame" буюу фреймыг хаягдах ба хэрэглэгчийн интерфэйс хариу үйлдэл хийхгүй, ажиллагаагүй болно.

Аппынхаа хөгжүүлэгчийн цэсийн Show Perf Monitor гэсэн дээр очоорой. Хоёр өөр төрлийн фреймын хурд байгааг та тэндээс харах болно.

JS фрейм хурд (JavaScript thread)

Ихэнх React Native аппликейшнууд дээр JavaScript thread дотор гол логик нь явагдаж байдаг. Тэнд React аппликейшн чинь байж, API calls хийгдэж, хүрч мэдрэх үйлдүүд гэх мэт зүйлс хийгддэг... Натив артай харагдацад орсон шинэчлэлүүд нь бөөгнөрч, фреймын ногдсон хугацаа дуусахаас өмнө event loop давталт бүрийн төгсгөл дэх натив тал руу илгээгддэг. Хэрэв JavaScript thread ямар нэг фреймд хариу үйлдэл хийхгүй бол үүнийг хаягдсан фрейм гэж үзнэ. Жишээ нь, та цогц хийгдсэн нэг аппликейшны суурь компонент дээр this.setState-ыг дуудвал тооцооллын хувьд хохиролтой компонентын дэд модыг дахин рендэр хийх ба магадгүй 200 миллисекунд зарцуулж, 12 фрейм хаягдах болно. JavaScript дээр ажилладаг анимейшнууд энэ үед хөдөлгөөнгүй болно. 100 миллисекундээс удвал хэрэглэгч түүнийг анзаарч байдаг.

Энэ явдал ихэвчлэн Navigator шилжилтийн үед болдог. Та шинэ зам push хийх үед тухайн харагдацыг хадгалах зорилгоор натив тал руу зөв команд явуулахын тулд JavaScript thread нь шаардлагатай бүх компонентыг рендэр хийх хэрэгтэй болно. Хэдэн фрейм авч болчхоод jank болох нь бий. Учир нь шилжилт нь JavaScript thread-ээр зохицуулагдаж байгаа. Заримдаа компонентууд componentDidMount дээр нэмж ажиллах ба ингэснээр шилжилтийн үед хоёр дахь гацалт үүсэх шалтгаан болдог.

Өөр нэг жишээ бол дэлгэцэд хүрэх үйлдлийн хариу үйлдлийн жишээ юм. Хэрэв та JavaScript thread дээр олон тоон фрейм дамнан ажиллаж байгаа бол TouchableOpacity-д хариу өгөхдөө удаан байгааг анзаарсан байх. Яагаад гэвэл JavaScript thread нь завгүй байгаа тул гол хэсгээс илгээсэн шинэ хүрэх үйлдлийг боловсруулж чадахгүй байгаа юм. Үр дүнд нь TouchableOpacity дэлгэцэд хүрэх үйлдэлд хариу үзүүлэхгүй, бүдгэрлийг тохируулах командыг натив харагдац руу илгээхгүй.

UI фрейм хурд (гол thread)

NavigatorIOS-ын ажиллагаа нь Navigator-аас илүү болохыг олон хүн анзаарсан байдаг. Үүний учир нь шилжилтийн анимейшн нь гол thread дээр хийгдсэн байдагт оршино. Тиймдээ ч JavaScript thread дээрх фрейм хаягдах явдал тэдэнд садаа болдоггүй.

Мөн та JavaScript thread түгжигдсэн үед ScrollView ашиглан чөлөөтэй дээш, доош гүйлгэж болдог. Яагаад гэхээр ScrollView нь мөн гол thread дээр байдаг юм. Гүйлгэх үйлдлийн тухай мэдээлэл JS thread рүү илгээгдэх ч гүйлгэх үйлдэл хийгдэхэд тухайн мэдээлэл хүрч очсон эсэх нь хамаагүй юм.

Ажиллагаатай холбоотой асуудлуудын түгээмэл шалтгаан

Хөгжүүлэгчийн горим дээр ажиллуулах (dev=true)

JavaScript thread-ын ажиллагаа нь хөгжүүлэгчийн горимд ажиллаж байгаа үед их саатдаг. Үүнээс зайлсхийх боломжгүй. Пропын төрлийг баталгаажуулах, өөр олон төрлийн баталгаажуулалт хийх гэх мэтчилэн анхааруулга, алдааны мессеж гаргахын тулд их ажил хийгдэх хэрэгтэй болдог. release builds дээр ажиллагаагаа үргэлж тест хийж байгаарай.

console.log ашиглах

Багц апп хийж байгаа үед эдгээр мэдэгдэл нь JavaScript thread дээр түгжрэл үүсгэх магадлалтай. redux-logger гэх мэт дибаг хийх сангаас дуудах хүртэл үүнд хамаатай. Тиймээс нэгтгэж багцлахын өмнө арилгах хэрэгтэй. Та babel plugin ашиглан бүх console.* -уудыг арилгаж болно. Эхлээд та npm i babel-plugin-transform-remove-console --save-dev-тай суулгаад тэгээд үүн шиг .babelrc файлаа засварлана:

{
  "env": {
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}

Таны бэлэн эсвэл хийгдэж байгаа төслийн бүх console.*calls-ыг устах болно.

ListView эхний рендэр хийхдээ хэт удаан эсвэл урт жагсаалтыг гүйлгэх ажиллагаа нь тааруу байх

Шинэ FlatList эсвэл SectionList компонентыг оронд нь ашигла. API-ыг энгийг болгохоос гадна шинэ жагсаалтын компонент нь ажиллагааг огцом сайжруулдаг. Гол нэг нь хэдэн ч эгнээ бүхий жагсаалтыг хадгалах байнгын санах ой болж өгдөг.

Хэрэв таны FlatList рендэр хийхдээ удаан байвал getItemLayout суулгасан эсэхээ шалгаарай. Энэ нь рендэр хийсэн зүйлийг хэмжих процессыг алгасдаг тулд рендэр хийх хурдыг нэмж өгдөг.

Бараг өөрчлөгддөггүй харагдацыг дахин рендэр хийх үед JS FPS (хурд) унах

Хэрэв та ListView ашиглаж байгаа бол rowHasChanged функцийг ажиллуулах хэрэгтэй. Энэ нь аливаа эгнээг дахин рендэр хийх эсэхийг хурдан шийдсэнээр цаг хэмнэдэг. Хэрэв байнгын тогтмол өгөгдлийн бүтэц ашиглаж байгаа бол их амар байх болно. Үүний нэгэн адил та мөн shouldComponentUpdate ажиллуулж яг ямар нөхцөлд компонентыг дахин рендэр хийхийг хүсэж буйгаа тодорхойлж өгч болно. Рендэр хийсний буцаах дүн нь пропс болон төлөвөөс бүрэн хамаарах ба шинэ компонент бичиж байгаа бол та PureComponent-ыг ашиглах боломжтой. Дахин сануулахад, өөрчлөгддөггүй байнгын өгөгдлийн бүтэц нь хурдтай ажиллахад нь тус болдог. Хэрэв та урт жагсаалт бүхий зүйл дээр ажиллах шаардлагатай бол компонентоо бүхэлд нь дахин рендэр хийх нь илүү хурдан байх болно. Код ч бага бичнэ.

JavaScript thread дээр олон зүйл хийснээс болж JS thread-ын FPS унах

"Slow Navigator transitions" нь үүний гол нотолгоо юм. Гэхдээ өөр үед ч бас тохиолдох боломжтой. InteractionManager ашиглах нь зөв арга ч гэлээ, анимейшны үед ажиллагаа саатах нь хэрэглэгчид таагүй гэж үзэж байвал та LayoutAnimation ашиглаж болно.

Та useNativeDriver: true-ыг тохируулахгүй бол Animated API нь JavaScript thread дээр нэн шаардлагатай гол фрейм бүрийг тооцоолдог. LayoutAnimation нь Core Animation-ыг дэмжин ажиллах ба JS thread болон гол thread-ын фрейм хаягдах эсэх нь үүнд нөлөөлдөггүй.

Үүнийг ашигласан нэг тохиолдол нь модалыг хөдөлгөөнт оруулсан (дээрээс гулган нэвт гэрэлтэх давхарга болон замхардаг) үйлдэл юм. Ажиллаж эхлээд, сүлжээний хэд хэдэн хариу хүлээн авч байх үедээ модалд агуулагдаж буй зүйлсийг рендэр хийн модал нээгдсэн харагдацыг нь шинэчилнэ. LayoutAnimation-ыг хэрхэн ашиглах тухай Анимейшн хэсгээс уншина уу.

Санамж:

  • LayoutAnimation-ыг ажиллуулаад орхих ("байнгын байх") анимейшнд л зөвхөн ашиглаж болно. Хэрэв өөрчлөгддөг байх бол та Animated ашиглах нь зүйтэй.

Дэлгэц дээр гарч буй зүйлийг солих (гүйлгэх, орчуулах, эргүүлэх) үед UI thread FPS унах

Зураг дээр нэвт харагдах суурь дээр бичсэн текст эсвэл фрейм бүр дээр харагдацыг дахин зурах алфа үйлдэл хийх хэрэгтэй үед ийм зүйл түгээмэл тохиолддог. shouldRasterizeIOS эсвэл renderToHardwareTextureAndroid-ыг идэвхжүүлбэл их нэмэх болно.

Гэхдээ хэтрүүлж ашиглаж болохгүй, эс бөгөөс санах ойн хэрэглээ явж өгнө. Эдгээр пропсийг ашиглаж байгаа үед ажиллагаагаа хянаж, санах ойн хэрэглээгээ хянах хэрэгтэй. Харагдацыг дахин хөдөлгөх төлөвлөгөөгүй байгаа бол идэвхгүй болгож болно.

Зургийн хэмжээсийг хөдөлгөөнд оруулах үед UI thread FPS унах

iOS дээр та зургийн компонентын өргөн, өндрийг өөрчлөх бүрт ориг зураг нь дахин огтлогдож, хэмжээс нь өөрчлөгдөж байдаг. Энэ нь их төвөгтэй ажил. Ялангуяа том зурагны хувьд. Оронд нь transform: [{scale}] ашиглаж хэмжээг нь хөдөлгөөнд оруулбал амар байх болно. Үүнийг ашиглаж болох нэг тохиолдол нь та нэг зураг дээр дарахад тухайн зураг дэлгэц дүүрэн гарч томрох үйлдэл юм.

TouchableX хариу үйлдэл хийхгүй байна

Заримдаа бүдгэрлийг тохируулах, дэлгэцэд хүрэх үед хариу үйлдэл хийх компонентыг тодруулах үйлдэл хийж байгаа нэг фреймдээ үйлдэл хийхэд onPress товч гарах хүртэл тухайн эффектийг харах боломжгүй байдаг. onPress нь их ажилд үр дүнгээ өгч, цөөн фрейм хаях setState хийвэл ийм зүйл болно. Үүнийг шийдэх арга нь requestAnimationFrame доторх onPress -ын аливаа үйлдлийг дуусгах юм:

handleOnPress() {
  requestAnimationFrame(() => {
    this.doExpensiveAction();
  });
}

Navigator шилжилт удаан байх

Дээр дурдсанчлан, Navigator анимейшныг JavaScript thread удирддаг. "баруунаас түлхэх" шилжилтийг төсөөл. Фрейм бүр дээр шинэ үзэгдэл баруунаас зүүн рүү шилжиж, дэлгэцээс илүү гарч байна үзье (x-offset of 320). Үзэгдэл x-offset 0 болох үед зогсож байна. Энэхүү шилжилтийн фрейм бүрт JavaScript thread нь шинэ x-offset-ын тухайн мэдээллийг гол хэсэг рүү илгээдэг. Хэрэв JavaScript thread түгжигдсэн бол үүнийг хийж чадахгүй ба тухайн фрейм дээр ямар нэг шинэчлэл хийгдэхгүй, анимейшн гацна.

Үүний нэг шийдэл нь JavaScript дээр суурилсан анимейшныг гол хэсэг рүү буулгадаг болгох юм. Хэрэв дээрх жишээ шиг ижил зүйлийг хийх байсан бол шилжилт эхлэх үеийн шинэ үзэгдлийн бүх x-offset жагсаалтыг тооцоолж, оновчтой ажиллуулахаар гол хэсэг рүү илгээнэ. JavaScript thread нь үүргээс чөлөөлөгдсөн тул үзэгдлийг рендэр хийх үедээ цөөн хэдэн фрейм хаях нь тийм ч чухал биш болно. Та гоё шилжилтийг хараад үүнийг анзаарах ч үгүй байх.

Шинэ React Navigationсангийн гол зорилгуудын нэг нь үүнийг шийдэх юм. React Navigation-ын харагдац нь натив компонентууд болон Animated сан ашиглаж, native thread дээр секундэд 60 фрейм ажиллуулж чадна.

Мэдээлэгч

Цаанаас суулгасан мэдээлэгчийг ашиглан JavaScript thread болон гол thread дээр хийсэн ажлын талаар дэлгэрэнгүй мэдээллийг эгнүүлэн харах боломжтой. Debug цэсний Perf Monitor гэдгийг сонгож орно.

iOS дээр бол Instruments их хэрэг болдог. Android дээр бол systrace ашиглана.

Гэхдээ эхлээд Хөгжүүлэгчийн горим унтраастай эсэхийг шалгаарай! Танд аппикейшны лоог дээр __DEV__ === false, development-level warning are OFF, performance optimizations are ON гэж харагдана.

JavaScript-ын мэдээлэл авах өөр нэг арга нь дибаг хийж байх үедээ Chrome profiler ашиглах юм. Код Chrome дээр ачаалж байгаа учраас зөв үр дүн харуулахгүй ч хаана "түгжрэл" үүсээд байгаа тухай ерөнхий мэдээллийг өгч чадна. Chrome's Performance доорх profiler гэснийг ажиллуулна. User Timing гэсэн дээр гал асч буй зураг гарна. Хүснэгт хэлбэрээр дэлгэрэнгүй харахыг хүсвэл Bottom Up гэсэн дээр дарж, зүүн дээд цэсээс DedicatedWorker Thread-ыг сонгоорой.

Android UI ажиллагааны тухай systrace ашиглан мэдээлэл авах

Android нь 10k+ гаруй төрлийн утас дээр ажилладаг ба програм рендэрийн хувьд ерөнхий ажиллагаатай байдаг. Ажиллах хүрээний бүтцээс шалтгаалж, мөн олон янзын төхөөрөмж дээр ажилладаг байхаар хийгдсэн байдаг тул iOS-тай харьцуулахад хязгаарлагдмал байдаг. Заримдаа сайжруулах шаардлагатай зүйлс гарах ба ихэнхдээ тэд нар нь натив кодны алдаа биш байдаг!

Энэ jank-ыг дибаг хийх эхний алхам бол 16 миллисекундийн фреймын хугацаанд таны цаг хаана нь зарцуулагдаж байгаа вэ гэдэгт хариулах юм. Үүний тулд бид Android дээр systrace гэх хэрэгсэл ашиглана.

systrace нь тэмдэглэгчтэй мэдээлэгч хэрэгсэл юм (Android платформын багцыг суулгах үед давхар суудаг). Мэдээлэл авах код хэсгийн эхлэл/төгсгөлийг тэмдэглэж болох ба өнгийн чарт хэлбэрээр харагддаг. Android SDK болон React Native нь стандарт тэмдэглэгчийн сонголттой байдаг.

1. Алдааны тэмдэглэгээг олох

Эхлээд гацаад байгаа төхөөрөмжөө USB ашиглан компьютертойгоо холбоно. Тэгээд мэдээлэл авах навигаци/анимейшн хэсгийн өмнө аваачаад доорх маягаар systrace ажиллуулна:

$ <path_to_android_sdk>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <your_package_name>

Энэхүү командыг тайлбарлавал:

  • time нь секундээр илэрхийлсэн тэмдэглэгээ хамрах хугацаа

  • sched, gfx, болон view нь бидэнд хэрэгтэй android SDK таагууд юм (хураасан тэмдэглэгээнүүд. sched нь утасны core бүрт юу ажиллаж байгаа тухай мэдээлэл өгнө. gfx нь фреймын хил хязгаар гэхчлэн график мэдээлэл өгөх бол view нь хэмжээ, layout, зургийн тухай мэдээлэл өгнө.

  • -a <your_package_name> нь тухайн аппд зориулсан тэмдэглэгчийг идэвхжүүлнэ. Тэр дундаа React Native framework-т хийгдсэнүүдийг нь. Your_package_name -ыг аппын AndroidManifest.xml хэсгээс олох ба com.example.app гэсэн байна.

Алдааны тэмдэглэгээг хайж эхлэх үед хэрэгтэй анимейшн, харилцан үйлдлээ хийгээд үзээрэй. Алдаа хайх процесс дууссаны дараа systrace нь хөтөч дээр нээх холбоосыг танд өгнө.

2. Алдааны тэмдэглэгээг унших

Алдааны тэмдэглэгээг хөтөч дээр нээсний дараа (Chrome ашиглахыг зөвлөе) үүн шиг харагдана:

Жишээ

Сануулга: WASD товчлууруудыг ашиглаж томруулж, устгаарай.

Хэрэв алдааны .html файл зөв нээгдэхгүй бол хөтчийнхөө console хэсгээс үүнийг хараарай:

ObjectObserveError

Object.observe нь хүлээн зөвшөөрөгдөөгүй тул үүнийг та Google Chrome Tracing tool-ээр нээнэ. Үүний тулд:

  • chrome дээр тааб нээнэ chrome://tracing
  • ачаална
  • Өмнө командаас гарсан html файлаа сонгоно.

VSync тодруулагчийг идэвхжүүлэх

Дэлгэцийн баруун дээд буланд болох цонхыг зөвлөж, фреймын захыг 16 мс болгож тодруулаарай.

 VSync Highlighting идэвхжүүлэх

Та дээрх шиг хар цагаан зураастай хэсгийг хаана. Хэрэв харагдахгүй бол та өөр төхөөрөмж дээр үзээрэй. Samsung утас vsyncs ажиллуулахад асуудал гарч байсан бол Nexus-ын хувьд харьцангуй гайгүй байдаг.

3. Явцаа шинжих

Пакэжийнхаа нэрийг олох хүртэл гүйлгэнэ. Миний хувьд com.facebook.adsmanager-д анализ хийж байна. book.adsmanager гэж харагдаж байгаа учир нь нэр оруулах хэсэг нь үгийн тоог хязгаарлачихсан юм.

Та баруун талын timeline-тай хорших олон thread-үүд зүүн талд байгааг харж байна. Таны хэрэгтэй цөөн хэдэн thread байгаа нь UI thread (таны пакэжны нэр эсвэл хэрэглэгчийн интерфэйсийн нэрээрээ), mqt_js, болон mqt_native_modules. Хэрэв та Android 5+ ажиллаж байгаа бол Render Thread мөн хэрэг болно.

  • UI Thread. Энд android-ын хэмжээс/layout/зурах үйлдэл байна. Нэр нь таны пакэжийн нэр (Миний жишээ дээр бол book.adsmanager) эсвэл UI Thread-ын нэр нь байна. Энд харж байгаа эвентүүд нь үүн шиг харагдах ба Choreographer, traversals болон DispatchUI оролцоно:

    UI Thread example

  • JS Thread. Энд Javascipt ажиллаж эхэлнэ. Thread нэр нь таны төхөөрөмжөөс хамаарч mqt_js эсвэл <...> байна. Нэр байхгүй эсэхийг олж мэдэхийн тулд JSCall, Bridge.executeJSCall гэсэн бичгийг хайгаарай:

    JS Thread example

  • Native Modules Thread. Натив модуль энд дуудагдаж ажилладаг(UIManager г.м). Thread нэр нь mqt_native_modules эсвэл <...> гэсэн байна. Нэр байхгүй эсэхийг олж мэдэхийн тулд NativeCall, callJavaModuleMethod болон onBatchComplete гэсэн зүйлийг хайгаарай:

    Native Modules Thread Example

  • Бонус: Render Thread. Хэрэв та Android L (5.0) болон түүнээс дээш хувилбарын төхөөрөмж ашиглаж байгаа бол та аппликейшндаа Render Thread-тэй байна. Энэхүү thread нь жинхэнээсээ OpenGL команд өгч, хэрэглэгчийн интерфэйсийг гаргахад тусална. Thread нэр нь RenderThread юм уу <...> байна. Нэр байхгүй эсэхийг олж мэдэхийн тулд DrawFrame болон queueBuffer гэсэн зүйлийг хайгаарай:

    Render Thread Example

Буруутныг олох

Өв тэгш ажиллах анимейшн үүн шиг харагдана:

Smooth Animation

Өнгө өөрчлөгдөж байгаа бүхэн нь фрейм юм. Фрейм гаргахын тулд таны UI-гийн бүх ажил нь 16 миллисекундийн хугацаанд хийгдсэн байх ёстой гэдгийг анхаарна уу. Фреймийн заагтай ойр ямар ч thread ажиллахгүй байгааг анзаарна уу. Үүн шиг рендэр хийж байгаа аппликейшн бол 60 FPS хурдтай рендэр хийж байна гэсэн үг.

Үүн шиг харагдаж байвал харин тасарсан байна гэсэн үг:

Choppy Animation from JS

JS thread нь ер нь бол байнга ажиллаж байгааг анзаарна уу. Фреймын заагийг дамнан ажиллаж байна! Энэ апп нь 60 FPS хурдтай рендэр хийхгүй байна. Ийм тохиолдолд, Асуудал нь JS-т байна гэсэн үг.

Мөн ийм зүйл харагдаж болно:

Choppy Animation from UI

Энэ тохиолдолд UI болон render threads нь фрейм дамнан ажиллаж байна гэсэн үг. Фрейм бүр дээр бидний рендэр хийх гээд байгаа хэрэглэгчийн интерфэйс нь хэт их ачаалж байна. Энэ тохиолдолд асуудал нь рендэр хийж буй натив харагдацад байна гэсэн үг.

Та дараа дараагийн шатанд юу хийх ёстой талаар тодорхой мэдээлэлтэй боллоо.

JavaScript асуудлыг шийдэх

Хэрэв та JS алдаа олж таньсан бол ажиллуулж байгаа JS-тай холбоотой алдаатай байж болох сэжүүр хайгаарай. Дээр жишээ дээр бол бид фрейм бүрт RCTEventEmitter олонтаа дуудагдаж байгааг харж байна. Дээрх алдааны тэмдэглэгээнээс JS thread-ыг томруулж харвал:

Too much JS

Энд ямар нэг зүйл буруу байх шиг байна. Яагаад ийм олон дуудсан байна? Өөр эвентүүд юм уу? Хариулт нь таны кодоос хамаарна. Инэнх тохиолдолд та shouldComponentUpdate гэдгийг унших хэрэгтэй болно.

Native UI асуудлыг шийдэх

Хэрэв та натив UI-тай холбоотой асуудал олсон бол үндсэндээ хоёр сценари байна:

  1. Фрейм тус бүрт зурах гээд байгаа UI тань GPU буюу График боловсруулах нэгжид хэт их ачаалал өгч байна эсвэл
  2. Та анимейшн/харилцан үйлдлийн үед шинээр хэрэглэгчийн интерфэйс үүсгээд байна (гүйлгэх үед шинэ контент ачаалах г.м).
GPU хэт ачаалал

Эхний сценари дээр бол та UI thread эсвэл Render Thread-тэй холбоотой алдааны тэмдэглэгээ нь үүн шиг харагдана:

Overloaded GPU

Фреймын заагийг давсан DrawFrame нь их хугацаа эзэлж байгааг анзаарна уу. Энэ нь GPU-ыг команд гүйцэтгэхийг хүлээж буй хугацаа юм.

Үүнээс зайлсхийхийн тулд:

  • Олон хэсгээс бүрдсэн, хөдөлгөөнгүй контентыг хөдөлгөөнд оруулж эсвэл шилжүүлж байгаа бол 'renderToHardwareTextureAndroid' ашиглан шинжлэх (Navigator слайд/алфа анимейшн)
  • Цаанаас идэвхгүй тохиргоотой байдаг ч needsOffscreenAlphaCompositing-ыг та ашиглаагүй гэдгээ нэг шалгаарай. Энэ нь GPU дээр фрейм бүрийн ачааллыг инэнх тохиолдолд нэмдэг.

Хэрэв эдгээр нь танд туслахгүй бол GPU яг юу хийгээд байгааг нарийн шинжилж болно. Tracer for OpenGL ES-ыг уншаарай.

UI thread дээр шинээр харагдац үүсгэх

Хоёр дахь сценари дээр бол танд үүн шиг зүйл харагдана:

Creating Views

Эхний JS thread бага зэрэг ажиллаж байгаад натив модулийн thread дээр ажиллаад дараа нь UI thread дээр огцом шилжсэнийг харж байна. Та дараагийн харилцан үйлдэл хүртэл шинэ UI үүсгэхийг хойшлуулах эсвэл та хийж байгаа хэрэглэгчийн интерфэйсээ энгийн болгохоос нааш үүнийг засах амаргүй. React native-ийн баг үүнийг шийдэх дэд бүтцийн шийдэл олохоор ажиллаж байгаа бөгөөд шинээр UI үүсэж, гол thread-ын гадна тохиргоо хийгддэг болох юм. Ингэснээр харилцан үйлдэл нь алдаагүй зөв явагдана.

RAM bundles + inline requires

Хэрэв апп тань том хэмжээтэй бол Random Access Modules (RAM) bundle format болон inline requires-ийг ашиглах нь зүйтэй. Аппыг энгийн ашиглах үед төдийлөн нээгддэггүй олон дэлгэц бүхий апп хийж байгаа бол үүнийг ашиглах хэрэгтэй. Ерөнхийдөө, ажиллуулж эхэлснээс хойш бараг ашиглагдахгүй код ихтэй апп бол үүнийг ашиглана. Жишээ нь түвэгтэй олон профайл дэлгэцтэй, ашиглалт бага апп дээр бол ихэвчлэн аппын гол дэлгэцийг л хүмүүс ашиглах болно. RAM формат ашиглаж, тэдгээр функц, дэлгэцийг хэрэгтэй үед нь ашигладаг болгох боломжтой.

JavaScript ачаалах

React-native нь JS кодыг ажиллуулдаг. Код нь санах ойд ачаалж, танигдах ёстой. Хэрэв та 50мб bundle ачааллаа гэж бодоход ажиллахын өмнө 50мб нь ачаалж, танигдсан байх ёстой. RAM bundles-ын оновчтой болгосон нэг функц нь 50мб-аас ажиллуулж эхлэх үед хэрэгтэй хэсгээ ачаалах боломж олгодог ба дараа нь шаардлагатай үед нэмж ачаалах боломжтой байдаг.

Inline Requires

Inline requires нь модуль эсвэл файлыг хэрэгтэй үе хүртэл нь шаардах үйлдлийг хойшлуулахад хэрэг болдог. Энгийн жишээ нь нэг иймэрхүү харагдана:

VeryExpensive.js

import React, { Component } from 'react';
import { Text } from 'react-native';
// ... import some very expensive modules

// You may want to log at the file level to verify when this is happening
console.log('VeryExpensive component loaded');

export default class VeryExpensive extends Component {
  // lots and lots of code
  render() {
    return <Text>Very Expensive Component</Text>;
  }
}

Optimized.js

import React, { Component } from 'react';
import { TouchableOpacity, View, Text } from 'react-native';

let VeryExpensive = null;

export default class Optimized extends Component {
  state = { needsExpensive: false };

  didPress = () => {
    if (VeryExpensive == null) {
      VeryExpensive = require('./VeryExpensive').default;
    }

    this.setState(() => ({
      needsExpensive: true,
    }));
  };

  render() {
    return (
      <View style={{ marginTop: 20 }}>
        <TouchableOpacity onPress={this.didPress}>
          <Text>Load</Text>
        </TouchableOpacity>
        {this.state.needsExpensive ? <VeryExpensive /> : null}
      </View>
    );
  }
}

RAM format байхгүй байсан нь inline requires нь апп эхлүүлэх цагт нэмэр болдог. Учир нь VeryExpensive.js доторх код нь анх удаа шаардлагатай бол л ажилладаг.

RAM format-ыг идэвхжүүлэх

iOS дээр RAM format ашиглах нь дан индекс бүхий файл үүсгэх ба react native нь модулийг нэг нэгээр нь ачаална. Android дээр цаанаасаа модуль бүрт шинэ файлууд үүсгэх тохиргоотой байна. Та Android дээр iOS шиг нэг файл үүсгэдэг байхаар албаар тохируулж болно. Гэхдээ олон файл ашиглавал илүү дээр ажиллах ба санах ой бага эзэлнэ.

Xcode дотор RAM format-ыг идэвхжүүлэхийн тулд "Bundle React Native code and images" үүсгэнэ. ../node_modules/react-native/scripts/react-native-xcode.sh гэсний өмнө export BUNDLE_COMMAND="ram-bundle" нэмнэ:

export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh

Android дээр RAM format-ыг идэвхжүүлэхдээ android/app/build.gradle файлдаа өөрчлөлт хийнэ. Line-ийн өмнө apply from: "../../node_modules/react-native/react.gradle" project.ext.react блокийг нэмэх эсвэл өөрчилж өгнө:

project.ext.react = [
  bundleCommand: "ram-bundle",
]

Хэрэв та дан индекс бүхий файл Android дээр ашиглах хүсэлтэй байгаа бол доорх кодыг ашиглаарай:

project.ext.react = [
  bundleCommand: "ram-bundle",
  extraPackagerArgs: ["--indexed-ram-bundle"]
]

Урьдчилан ачаалах үйлдлийг тохируулах

Бид одоо RAM bundle-тай болсон болохоор хараахан ачаалж амжаагүй байгаа модуль тааралдах бүрт require. require гэсэн мессеж харагдах болно. Энэ нь ажиллуулж эхлэх үед их нөлөөлдөг. Яагаад гэвэл энэ үед аппаа эхлээд ажиллах үед хамгийн их require calls ирдэг. Аз болоход бидэнд урьдчилан ачаалах модулийн хэмжээг тохируулах боломж байдаг. Үүний тулд та ямар нэг хэлбэрийн inline require ажиллуулах хэрэгтэй.

Тохиргооны файлд пакэжир нэмэх

Өөрийн төсөлдөө пакэжир нэртэй хавтас үүсгээд config.js гэсэн дан файл үүсгэнэ. Ингээд доорх кодыг нэмнэ:

const config = {
  transformer: {
    getTransformOptions: () => {
      return {
        transform: { inlineRequires: true },
      };
    },
  },
};

module.exports = config;

Xcode-д export BUNDLE_CONFIG="packager/config.js" гэдгийг оруулна.

export BUNDLE_COMMAND="ram-bundle"
export BUNDLE_CONFIG="packager/config.js"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh

android/app/build.gradle файлдаа bundleConfig: "packager/config.js", оруулна.

project.ext.react = [
  bundleCommand: "ram-bundle",
  bundleConfig: "packager/config.js"
]

Эцэст нь та package.json доторх "scripts" гэсний доор байгаа "start"-ыг шинэчилнэ:

"start": "yarn react-native start --config packager/config.js",

Пакэжаа npm start ашиглан ажиллуулна. xcode болон react-native run-android гэх мэтээс шалтгаална dev packager нь автоматаар ажиллана гэдгийг анхаарна уу. Энэ нь npm start-ыг ашиглаагүй тулд тохиргоо хэрэггүй.

Ачаалсан модулийг шинжлэх нь

Үндсэн файл дотроо (index.(ios|android).js) та эхний импортыг хийсний дараа доорхыг нэмж болно:

const modules = require.getModules();
const moduleIds = Object.keys(modules);
const loadedModuleNames = moduleIds
  .filter(moduleId => modules[moduleId].isInitialized)
  .map(moduleId => modules[moduleId].verboseName);
const waitingModuleNames = moduleIds
  .filter(moduleId => !modules[moduleId].isInitialized)
  .map(moduleId => modules[moduleId].verboseName);

// make sure that the modules you expect to be waiting are actually waiting
console.log(
  'loaded:',
  loadedModuleNames.length,
  'waiting:',
  waitingModuleNames.length
);

// grab this text blob, and put it in a file named packager/modulePaths.js
console.log(`module.exports = ${JSON.stringify(loadedModuleNames.sort())};`);

Аппаа ажиллуулах үед console дотроос хичнээн модуль ачаалж, хичнээн нь хүлээгдэж байгаа вэ гэдгийг хараарай. Та moduleNames гэснийг уншаад ямар нэг анхаарал татах зүйлс байгаа эсэхийг шалгаарай. Inline requires нь импорт хийх үед анх удаа дуудагддаг гэдгийг анхаарна уу. Та апп ажиллаж эхлэх үед зөвхөн хэрэгтэй модуль ачаалж байгаа эсэхийг шинжих хэрэгтэй. Systrace-т өөрчлөлт хийж, асуудалтай requires-ийн алдааг олж болно.

require.Systrace.beginEvent = (message) => {
  if(message.includes(problematicModule)) {
    throw new Error();
  }
}

Апп бүр өөр хэдий ч эхний нүүрэнд хэрэгтэй модулийг ачаалах нь зүйтэй гэдэг нь ойлгомжтой биз. Хэрэв та сэтгэл дүүрэн байвал packager/modulePaths.js гэсэн файл руу loadedModuleNames-аа хийнэ.

config.js шинэчлэх

Шинээр үүсгэсэн modulePaths.js файлаа ашиглахын тулд бид packager/config.js буцаж очин шинэчлэх шаардлагатай.

const modulePaths = require('./modulePaths');
const resolve = require('path').resolve;
const fs = require('fs');

// Update the following line if the root folder of your app is somewhere else.
const ROOT_FOLDER = resolve(__dirname, '..');

const config = {
  transformer: {
    getTransformOptions: () => {
      const moduleMap = {};
      modulePaths.forEach(path => {
        if (fs.existsSync(path)) {
          moduleMap[resolve(path)] = true;
        }
      });
      return {
        preloadedModules: moduleMap,
        transform: { inlineRequires: { blacklist: moduleMap } },
      };
    },
  },
  projectRoot:ROOT_FOLDER,
};

module.exports = config;

RAM bundle үүсгэж байгаа үед тохиргоо дахь preloadedModules нь аль модуль нь урьдчилан ачаалсан вэ гэсэн тэмдэглээ хийгддэг. Requires ажиллаж эхлэхээс өмнө Bundle ачаалсан үед requires ажиллаж эхлэхээс өмнө тухайн модулиуд тэр дороо ачаалдаг. Хар жагсаалтад байгаа зүйлс нь inline-ад шаардлагагүй модулиудыг илэрхийлж байгаа юм. Учир нь тэднийг урьдчилан ачаалах нь ямар ч ашиггүй. Импорт үүсгэх үед inline require-тай холбоотойгоор javascript нь илүү цаг зарж байдаг.

Сайжруулалтыг хэмжих, тест хийх

Та одоо RAM format болон inline requires ашиглан аппаа хийж чадахаар боллоо. Ажиллуулахын өмнө нь болон дараа нь хэмжилт хийхээ мартуузай.