鴻蒙HarmonyOS應用開發初體驗

近期(4.12 ~ 4.25)鴻蒙OS正在舉行開發者日活動,趁機參加並瞭解一下鴻蒙OS的現狀和應用開發體驗。 developer.huawei.com/consumer/cn…css

image.png

1. 開發環境搭建


下載安裝IDE(當前版本 2.1 Beta3)

華爲爲Harmony應用開發提供了配套的IDE:DevEco Studio(心裏比較排斥這種帶Eco字眼兒的命名,PPT怎麼吹無所謂,開發工具咱能不能務實一點兒?)前端

下載IDE須要登陸Huawei帳號,我安裝的是Mac版,下載後的安裝過程仍是比較順暢的vue

image.png

啓動界面顯示DevEco Studio仍然是基於IntelliJ的定製IDEjava

下載SDK

跟Android同樣,IDE啓動第一件事情是下載Harmony SDKreact

image.png

每一個版本的SDK中都提供了三套API用來開發Java、Js、C++代碼,版本上須要保持一致。 不一樣的華爲設備對SDK版本有不一樣要求,好比在測試中發現,個人API4的代碼沒法運行在P40上,改成API5就OK了git

關於SDK源碼

須要注意,目前沒法經過SDKManager打包下載源碼,源碼須要經過gitee單獨下載程序員

gitee.com/openharmonyweb

這爲代碼調試帶來障礙,不知道後期是否能夠像Andoird那樣與SDK一塊兒打包下載源碼json

建立項目

Harmony主打多端協同,因此很重視設備多樣性,可面向不一樣設備建立模板項目markdown

Screen Shot 2021-04-18 at 10.28.09 AM.png

相比AndroidStudio,Harmony提供了更加豐富的項目模板,模板中除了UI之外還提供了部分數據層代碼,基本上是一個能夠二次開發的APP。

Screen Shot 2021-04-18 at 5.48.55 PM.png


2. 鴻蒙項目結構


IDE界面

試着建立了一個News Feature Ability(新聞流)的模板項目,成功在IDE中打開:

image.png

IDE窗口與AndroidStudio相似,值得一提的Harmony右邊提供的Preview窗口,能夠對xml或者Ablitiy文件進行預覽,有點Compose的Preview的感受,可是隻能靜態預覽,沒法交互

工程文件

image.png

工程文件和Android相似,甚至能夠找到一一對應的關係

Harmony Android 說明
entry app 默認啓動模塊(主模塊),至關於app_module
MyApplication XXXApplication 鴻蒙的MyApplication是AbilityPackage的子類
MainAbility MainActivity 入口頁。鴻蒙中將四大組件的概念統一成Ability
MainAbilityListSlice XXXFragment Slice相似Fragment,UI的基本組成單元
Component View Component類至關於View,後文介紹
config.json AndroidManifest.xml 鴻蒙使用json替代xml進行Manifest配置,配置項目差很少
resources/base/... res/... 包括Layout文件在內的各類資源文件依舊使用xml
resources/rawfile/ assets/ rawfile存儲任意格式原始資源,至關於assets
build.gradle build.gradle 編譯腳本,二者同樣
build/outpus/.../*.hap build/outputs/.../*.apk 鴻蒙的產物是hap(harmony application package)
解壓后里面有一個同名的.apk文件,
這後續是由於鴻蒙須要同時支持apk安裝的兼容方案

Ability

Ability是應用所具有能力的抽象,Harmony支持應用以Ability爲單位進行部署。一個應用由一個或多個FA(Feature Ability)或PA(Particle Ability)組成。FA有UI界面,提供與用戶交互的能力;而PA無UI界面,提供後臺運行任務的能力以及統一的數據訪問抽象

  • FA支持Page Ability:
    • Page Ability用於提供與用戶交互的能力。一個Page能夠由一個或多個AbilitySlice構成,AbilitySlice之間能夠進行頁面導航

image.png

  • PA支持Service Ability和Data Ability:
    • Service Ability:用於提供後臺運行任務的能力。
    • Data Ability:用於對外部提供統一的數據訪問抽象。

能夠感受到,各類Ability能夠對照Android的四大組件來理解

Harmony Android
Page Ability (FA) Activity
Service Ability (PA) Service
Data Ability(PA) ContentProvider
AbilitySlice Fragment

代碼一覽

MainAbility

以預置的News Feature Ability爲例子,這是一個擁有兩個Slice的Page Ability,經過Router註冊兩個Slice

public class MainAbility extends Ability {

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setMainRoute(MainAbilityListSlice.class.getName()); //添加路由:ListSlice
        addActionRoute("action.detail", MainAbilityDetailSlice.class.getName());//DetailSlice

        ...
    }
}
複製代碼

如下是在模擬器中運行兩個Slice的頁面效果

MainAbilityListSlice MainAbilityDetailSlice
image.png image.png

MainAbilityListSlice

主要看一下列表的顯示邏輯

public class MainAbilityListSlice extends AbilitySlice {

    ...
    
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_news_list_layout);
        initView();
        initData(); //加載數據
        initListener();
        newsListContainer.setItemProvider(newsListAdapter); //Adatper設置到View
        newsListAdapter.notifyDataChanged(); //刷新數據
    }


    private void initListener() {
        newsListContainer.setItemClickedListener((listContainer, component, i, l) -> {
            //路由跳轉"action.detail"
            LogUtil.info(TAG, "onItemClicked is called");
            Intent intent = new Intent();
            Operation operation = new Intent.OperationBuilder()
                    .withBundleName(getBundleName())
                    .withAbilityName("com.example.myapplication.MainAbility")
                    .withAction("action.detail")
                    .build();
            intent.setOperation(operation);
            startAbility(intent);
        });
    }
    
    private void initData() {
        ...
        totalNewsDatas = new ArrayList<>();
        newsDatas = new ArrayList<>();
        initNewsData();//填充newsDatas
        newsListAdapter = new NewsListAdapter(newsDatas, this);//設置到Adapter
    }

    ...
}
複製代碼

相似ListView的用法,經過Adatper加載數據; setItemClickedListener中經過路由跳轉MainAbilityDetailSlice。

Layout_news_list_layout佈局文件定義以下,ListContainer即ListView,是Comopnent的一個子類,Component就是HarmonyOS中的View

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:orientation="vertical">

    <ListContainer ohos:id="$+id:selector_list" ohos:height="40vp" ohos:width="match_parent" ohos:orientation="horizontal" />

    <Component ohos:height="0.5vp" ohos:width="match_parent" ohos:background_element="#EAEAEC" />

    <ListContainer ohos:id="$+id:news_container" ohos:height="match_parent" ohos:width="match_parent"/>


</DirectionalLayout>
複製代碼

看一下Adapter的實現, 繼承自BaseItemProvider

/** * News list adapter */
public class NewsListAdapter extends BaseItemProvider {
    private List<NewsInfo> newsInfoList;
    private Context context;

    public NewsListAdapter(List<NewsInfo> listBasicInfo, Context context) {
        this.newsInfoList = listBasicInfo;
        this.context = context;
    }

    @Override
    public int getCount() {
        return newsInfoList == null ? 0 : newsInfoList.size();
    }

    @Override
    public Object getItem(int position) {
        return Optional.of(this.newsInfoList.get(position));
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public Component getComponent(int position, Component componentP, ComponentContainer componentContainer) {
        ViewHolder viewHolder = null;
        Component component = componentP;
        if (component == null) {
            component = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_item_news_layout, null, false);
            viewHolder = new ViewHolder();
            Component componentTitle = component.findComponentById(ResourceTable.Id_item_news_title);
            Component componentImage = component.findComponentById(ResourceTable.Id_item_news_image);
            if (componentTitle instanceof Text) {
                viewHolder.title = (Text) componentTitle;
            }
            if (componentImage instanceof Image) {
                viewHolder.image = (Image) componentImage;
            }
            component.setTag(viewHolder);
        } else {
            if (component.getTag() instanceof ViewHolder) {
                viewHolder = (ViewHolder) component.getTag();
            }
        }
        if (null != viewHolder) {
            viewHolder.title.setText(newsInfoList.get(position).getTitle());
            viewHolder.image.setScaleMode(Image.ScaleMode.STRETCH);
        }
        return component;
    }

    /** * ViewHolder which has title and image */
    private static class ViewHolder {
        Text title;
        Image image;
    }
}
複製代碼

基本上就是標準的ListAdatper,把View替換成Component而已。

關於模擬器

代碼完成後能夠再模擬器中運行。關於模擬器有幾點想說的:

  1. Harmony的模擬器啓動很是快,無需下載鏡像,由於這個模擬器並不是本地運行,而只是一個遠端設備的VNC,所以必須在線使用,並且不夠流暢時有丟幀現象。雖然真機調試效果更好,但不是人人都買得起P40的

  2. 模擬器嵌入到IDE窗口顯示(像Preview窗口同樣),非獨立窗口,這會帶來一個問題,當同時打開多個IDE時,模擬器可能會顯示在另外一個IDE中(就像Logcat跑偏同樣)。

  3. 想使用模擬器必須進過開發者認證,官方推薦使用銀行卡認證。模擬器遠端連接的是一臺真實設備,難道是爲將來租用設備要計費??image.png記得之前看過一篇文章,若是是來自國外地區的註冊帳號能夠免認證使用模擬器,可是懶得折騰了


3. 開發JS應用


除了Java,鴻蒙還支持基於JS開發應用,藉助前端技術完善其跨平臺能力。

鴻蒙爲JS工程提供了多種經常使用UI組件,可是沒有采用當下主流的react、vue那樣JS組件,仍然是基於CSS3/HTML5/JS這種傳統方式進行開發。JS工程結構以下

image.png

目錄 說明
common 可選,用於存放公共資源文件,如媒體資源、自定義組件和JS文檔等
i18n 可選,用於存放多語言的json文件
pages/index/index.hml hml文件定義了頁面的佈局結構,使用到的組件,以及這些組件的層級關係
pages/index/index.css css文件定義了頁面的樣式與佈局,包含樣式選擇器和各類樣式屬性等
pages/index/index.js js文件描述了頁面的行爲邏輯,此文件裏定義了頁面裏所用到的全部的邏輯關係,好比數據、事件等
resources 可選,用於存放資源配置文件,好比:全局樣式、多分辨率加載等配置文件
app.js 全局的JavaScript邏輯文件和應用的生命週期管理。

4. 跨設備遷移


經過前面的介紹,可能感受和Android大同小異,可是HarmonyOS最牛逼之處是多端協做能力,例如能夠將Page在同一用戶的不一樣設備間遷移,實現無縫切換。

以Page從設備A遷移到設備B爲例,遷移動做主要步驟以下:

  • 設備A上的Page請求遷移。
  • HarmonyOS回調設備A上Page的保存數據方法,用於保存遷移必須的數據。
  • HarmonyOS在設備B上啓動同一個Page,並回調其恢復數據方法。

經過調用continueAbility()請求遷移。以下,獲取設備列表,配對成功後請求遷移

doConnectImg.setClickedListener( 
	clickedView -> { 
		// 經過FLAG_GET_ONLINE_DEVICE標記得到在線設備列表 
		List deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); 
		if (deviceInfoList.size() < 1) { 
			WidgetHelper.showTips(this, "無在網設備"); 
		} else { 
			DeviceSelectDialog dialog = new DeviceSelectDialog(this); 
			// 點擊後遷移到指定設備 
			dialog.setListener( 
				deviceInfo -> { 
					LogUtil.debug(TAG, deviceInfo.getDeviceName()); 
					LogUtil.info(TAG, "continue button click"); 
					try { 
						// 開始任務遷移 
						continueAbility(); 
						LogUtil.info(TAG, "continue button click end"); 
					} catch (IllegalStateException | UnsupportedOperationException e) { 
						WidgetHelper.showTips(this, ResourceTable.String_tips_mail_continue_failed); 
					} 
					dialog.hide(); 
				}); 
			dialog.show(); 
		} 
	});
        
複製代碼

Page遷移涉及到數據傳遞,此時須要藉助IAbilityContinuation進行通訊。

跨設備通訊 IAbilityContinuation

跨設備遷移的Page須要實現IAbilityContinuation接口。

Note: 一個應用可能包含多個Page,僅須要在支持遷移的Page中經過如下方法實現IAbilityContinuation接口。同時,此Page所包含的全部AbilitySlice也須要實現此接口。

public class MainAbility extends Ability implements IAbilityContinuation { 
    ... 
    @Override 
    public void onCompleteContinuation(int code) {} 
 
    @Override 
    public boolean onRestoreData(IntentParams params) { 
        return true; 
    } 
 
    @Override 
    public boolean onSaveData(IntentParams params) { 
        return true; 
    } 
 
    @Override 
    public boolean onStartContinuation() { 
        return true; 
    } 
}
public class MailEditSlice extends AbilitySlice implements IAbilityContinuation { 
    ... 
    @Override 
    public boolean onStartContinuation() { 
        LogUtil.info(TAG, "is start continue"); 
        return true; 
    } 
 
    @Override 
    public boolean onSaveData(IntentParams params) { 
        ... 
        LogUtil.info(TAG, "begin onSaveData:" + mailData); 
        ... 
        LogUtil.info(TAG, "end onSaveData"); 
        return true; 
    } 
 
    @Override 
    public boolean onRestoreData(IntentParams params) { 
        LogUtil.info(TAG, "begin onRestoreData"); 
        ... 
        LogUtil.info(TAG, "end onRestoreData, mail data: " + cachedMailData); 
        return true; 
    } 
 
    @Override 
    public void onCompleteContinuation(int i) { 
        LogUtil.info(TAG, "onCompleteContinuation"); 
        terminateAbility(); 
    } 
}
複製代碼
  • onStartContinuation(): Page請求遷移後,系統首先回調此方法,開發者能夠在此回調中決策當前是否能夠執行遷移,好比,彈框讓用戶確認是否開始遷移。

  • onSaveData(): 若是onStartContinuation()返回true,則系統回調此方法,開發者在此回調中保存必須傳遞到另外設備上以便恢復Page狀態的數據。

  • onRestoreData(): 源側設備上Page完成保存數據後,系統在目標側設備上回調此方法,開發者在此回調中接受用於恢復Page狀態的數據。注意,在目標側設備上的Page會從新啓動其生命週期,不管其啓動模式如何配置。且系統回調此方法的時機在onStart()以前。

  • onCompleteContinuation(): 目標側設備上恢復數據一旦完成,系統就會在源側設備上回調Page的此方法,以便通知應用遷移流程已結束。開發者能夠在此檢查遷移結果是否成功,並在此處理遷移結束的動做,例如,應用能夠在遷移完成後終止自身生命週期。

以Page從設備A遷移到設備B爲例,詳細的流程以下:

  1. 設備A上的Page請求遷移。
  2. 系統回調設備A上Page及其AbilitySlice棧中全部AbilitySlice實例的IAbilityContinuation.onStartContinuation()方法,以確認當前是否能夠當即遷移。
  3. 若是能夠當即遷移,則系統回調設備A上Page及其AbilitySlice棧中全部AbilitySlice實例的IAbilityContinuation.onSaveData()方法,以便保存遷移後恢復狀態必須的數據。
  4. 若是保存數據成功,則系統在設備B上啓動同一個Page,並恢復AbilitySlice棧,而後回調IAbilityContinuation.onRestoreData()方法,傳遞此前保存的數據;此後設備B上此Page從onStart()開始其生命週期回調。
  5. 系統回調設備A上Page及其AbilitySlice棧中全部AbilitySlice實例的IAbilityContinuation.onCompleteContinuation()方法,通知數據恢復成功與否。

5. 總結和感想


  1. 從SDK到IDE與Android都高度類似,任何Android開發者基本上就是一個準鴻蒙程序員
  2. AndroidStudio的功能迭代很快,DevEco Studio在功能上還存在較大差距
  3. 須要實名認證開發者以後才能使用IDE的各類完整功能,心裏抗拒
  4. 源碼須要另外下載,對調試不友好
  5. 當前還不支持Kotlin。大勢所趨,因此將來必定會支持,並且Kotlin是開源的問題不大
  6. JS UI框架的技術架構一樣有些過期
  7. 殺手鐗是對多端協做的支持,但這可能須要更多的廠商加入才能真正發揮威力

目前人們對於鴻蒙的態度呈現兩極化,有的人追捧有的人貶低,我以爲都大可沒必要,多給鴻蒙一些空間和耐心,靜觀其變、樂見其成。固然首選須要華爲作到本身不主動炒做,真正靜下心來打磨鴻蒙,只要華爲有決心有耐心,做爲開發者的咱們爲何不支持呢?

Harmony線上挑戰賽

伴隨開發者活動日,鴻蒙還舉辦了多輪線上挑戰賽活動(目前還在進行中),難度不高參與即能完成,且中獎率很高(親測),有興趣能夠參與一下,但願個人好運傳遞給你

活動詳情:mp.weixin.qq.com/s/3IrZGZkm1…

image.png

相關連接

相關文章
相關標籤/搜索