Join our FREE personalized newsletter for news, trends, and insights that matter to everyone in America

Newsletter
New

Treasure Case Sharing Of Harmonyos 5 Development — Practical Tips For Application Architecture

Card image cap

HarmonyOS Application Architecture Practice: Layered Design and Thread Communication Explained

Hello everyone! Today, let's talk about those architectural design techniques in HarmonyOS development that are 'mentioned in the official docs but hard to find in real projects.' Combining official documentation (Link 1, Link 2), I'll use real code examples and plain explanations to help you implement layered architecture and thread communication in your project, so you can say goodbye to 'knowing the theory but not the code'!

1. Layered Architecture: How to Use the Three-Layer Design?

HarmonyOS's layered architecture (Product Customization Layer, Basic Feature Layer, Common Capability Layer) is not just talk—the core is to reduce coupling and enable multi-end reuse. Let's look directly at the code structure:

MyApp/    
│    
├── entry/          # Product Customization Layer: Device-specific entry    
│   ├── phone/      # Phone UI and logic    
│   └── tablet/     # Tablet customization    
│    
├── features/       # Basic Feature Layer: Pluggable business modules    
│   ├── news/       # News module (independent HAP)    
│   └── settings/   # Settings module    
│    
└── common/         # Common Capability Layer    
    ├── components/ # Common UI components    
    ├── utils/      # Utility library    
    └── network/    # Network request encapsulation  

Key Code Example:

1. Extracting Network Requests in the Common Capability Layer

// common/network/Request.ts    
export class Request {    
  static async fetch(url: string): Promise<any> {    
    try {    
      const response = await http.createHttp().request(url);    
      return response.data;    
    } catch (err) {    
      // Unified error handling    
      console.error("Network error:", err);    
    }    
  }    
}  

2. Basic Feature Layer Calls Common Capability

// features/news/NewsViewModel.ts    
import { Request } from '../../common/network/Request';    
  
class NewsViewModel {    
  async loadNews() {    
    const data = await Request.fetch('https://api.news.com/list');    
    // Business logic processing...    
  }    
}  

3. Product Customization Layer Loads Modules by Device

// entry/phone/resources/base/profile/main_pages.json    
{    
  "src": [    
    "pages/PhoneHome",       // Phone home page    
    "pages/NewsPage?module=news" // Dynamically load news module    
  ]    
}  

Why design like this?

  • Need to change network requests? Just modify common/network, no impact on business code.
  • Add a new device (like a watch)? Copy entry/phone and change to entry/watch for custom UI.
  • Remove the news module? Just remove the HAP package in features/news.

2. Thread Communication: How Can Sub-Threads Safely Update the UI?

HarmonyOS UI updates must be on the main thread (also called the UI thread), but time-consuming operations (network requests/database reads and writes) should be on sub-threads. The official recommendation is to use TaskDispatcher and Emitter for communication.

Practical Scenario: Sub-thread Gets Data → Main Thread Refreshes UI

// In ViewModel    
import { emitter, TaskDispatcher } from '@ohos.base';    
import { Request } from '../common/network/Request';    
  
const UI_TASK_DISPATCHER = TaskDispatcher.getGlobalTaskDispatcher(TaskDispatcher.Priority.HIGH);    
  
class UserViewModel {    
  private userId: string = '';    
  
  // 1. Get data in sub-thread    
  async fetchUserData() {    
    const backgroundTask: TaskDispatcher = TaskDispatcher.createBackgroundTaskDispatcher();    
    backgroundTask.asyncDispatch(() => {    
      const data = Request.fetch(`https://api.user.com/${this.userId}`);    
      // 2. Send data to main thread via Emitter    
      emitter.emit('USER_DATA_LOADED', data);    
    });    
  }    
  
  // 3. Main thread listens for events    
  setupEventListener() {    
    emitter.on('USER_DATA_LOADED', (data) => {    
      UI_TASK_DISPATCHER.asyncDispatch(() => {    
        // Safely update UI    
        this.userInfo = data;    
        AppStorage.setOrCreate('userName', data.name); // Bind to UI component    
      });    
    });    
  }    
}  

Pitfall Guide:

  • Wrong example: Directly call AppStorage.set() in sub-thread → causes UI crash.
  • Correct way: Sub-thread sends event → main thread uses asyncDispatch to update data.
  • Performance optimization: Frequent updates? Use @State + @Watch to refresh components locally.

3. Modular Design: When to Use HAP, HAR, or HSP?

The official docs often mention these concepts. In real development, use them like this:

Type Scenario Code Example
HAP Independent feature module (e.g., settings) Configure in build-profile.json
with "type": "feature"
HAR Common utility library (no UI) Static library depended on by multiple HAPs
HSP Cross-app shared code Declare shared: true

Dynamically Load HAP Module (Common in Plugin Architecture)

// Dynamically load news module in entry    
import featureAbility from '@ohos.ability.featureAbility';    
  
const moduleName = 'news';    
featureAbility.dynamicImport(    
  `bundlename:${moduleName}`,     
  (err, data) => {    
    if (err) return;    
    // After successful load, jump to news page    
    router.pushUrl({ url: 'pages/NewsPage' });    
  }    
);  

4. Practical Tips Not Explicitly Stated in Official Docs

  1. Prevent 'Pollution' in the Common Capability Layer:
    • Prohibit reverse dependencies: common must not import code from entry or features.
    • How to check: Configure dependencies in build-profile.json:
"dependencies": {    
  "features/news": ">=1.0.0",    
  "common": ">=1.0.0"    
}  
  1. Killer for Multi-Device Adaptation: Use resource qualifiers to distinguish devices, such as:
    • news_page.phone.ets → News page for phone
    • news_page.tablet.ets → Tablet version (large screen layout) Automatically matches device type for packaging at compile time!

Conclusion

HarmonyOS's layered and modular design takes some effort to set up initially, but it's really worth it for later maintenance! Suggestions:

  1. Firmly sink common code into the common layer;
  2. After sub-thread operations, always use Emitter to update UI on the main thread;
  3. Try to split new features into HAPs for easy hot updates.

Encountered any pitfalls? Feel free to comment and discuss!

Keep encapsulation, refuse coupling, see you next time! ????