JS FA calls PA (show local album pictures)

Posted by miltonbanks on Thu, 03 Mar 2022 15:24:44 +0100

1, Effect display

2, Project introduction

\qquad This project uses ArkUI (JS) to develop the interface, uses JS FA to call the interface function of JAVA PA, pulls the local album through JAVA, selects the picture and returns, copies the picture to the directory accessible by JS IMAGE component, and returns the picture path to JS. Finally, the function of displaying local album pictures in JS interface is realized.

\qquad The project provides two schemes, internal capability and local particle capability. The ideas of the two schemes are basically the same, and the final effect is the same.

\qquad Through this project, I will share with you my current experience of using JS FA to call JAVA PA function and the idea of JS displaying album photos. At the same time, I hope you can point out the shortcomings in my code. Part of the code in the project comes from various tutorials.

3, Code structure display

  1. Java end
    • getPhotoInternalAbility: interact with JS side in Internal Ability mode
    • getPhotoLocalParticleAbility: interacts with JS side in LocalParticleAbility mode
    • Utils: tool class, saving photos
    • MainAbility: main Ability
  2. Js side: basic display page (index.hml, index.css, index.js)

4, Key issues

4.1 authority issues

To access user media files, you need to apply for permission related to media files.

jurisdictionexplain
ohos.permission.MEDIA_LOCATIONAllow apps to access geographic location information in user media files
ohos.permission.READ_MEDIAAllow the application to read the media file information in the user's external storage
ohos.permission.WRITE_MEDIAAllow the application to read and write the media file information in the user's external storage

config.json permission configuration:

    "reqPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA"
      },
      {
        "name": "ohos.permission.WRITE_MEDIA"
      },
      {
        "name": "ohos.permission.MEDIA_LOCATION"
      }
    ]

You can choose to add dynamic application permission, and there will be permission prompt when adding.

// Dynamic application permission
private void requestPermissions() {
    String[] permissions =
            {
                    SystemPermission.MEDIA_LOCATION,
                    SystemPermission.WRITE_MEDIA,
                    SystemPermission.READ_MEDIA,
            };
    List<String> permissionFiltered =
            Arrays.stream(permissions)
                    .filter(permission -> verifySelfPermission(permission)
                            != IBundleManager.PERMISSION_GRANTED).collect(Collectors.toList());
    requestPermissionsFromUser(permissionFiltered.toArray(new String[permissionFiltered.size()]), 0);
}

4.2 page Jump problem

\qquad Like normal jump to other Ability pages, we need to specify the startup target through Intent.
\qquad Here, we build intent based on the ability to select pictures as needed and pass in startAbility to jump to the local album.

Intent intent = new Intent();
Operation opt=new Intent.OperationBuilder()
        .withAction(IntentConstants.ACTION_CHOOSE)
        .build();
intent.setOperation(opt);
intent.addFlags(Intent.FLAG_NOT_OHOS_COMPONENT);
intent.setType("image/*");
abilityContext.startAbility(intent,code); // code here is optional

\qquad The key point is to obtain the abilitycontext. It is easy to know that startAbility is the method in the abstract class abilitycontext, MainAbility inherits from AceAbility, and the parent class of AceAbility is the specific implementation class of abilitycontext.

\qquad The main thread (UI thread) context of our operation should be managed by MainAbility. At this time, we need to jump from one Ability to another. It is obvious that the acquisition of abilityContext is related to MainAbility.

\qquad By browsing the official documents, it is not difficult to find that the implementation of Internal Ability and LocalParticleAbility are closely related to MainAbility. There are registration and logout methods in both methods. Is the required parameter MainAbility?

4.2.1 Internal Ability mode

getPhotoInternalAbility

private AbilityContext abilityContext;

/**
 * Internal ability Register the interface.
 */
public static void register(AbilityContext abilityContext) {
    instance = new getPhotoInternalAbility();
    instance.onRegister(abilityContext);
}

private void onRegister(AbilityContext abilityContext) {
    this.abilityContext = abilityContext;
	...
}

MainAbility

@Override
public void onStart(Intent intent) {
    getPhotoInternalAbility.register(this);
    super.onStart(intent);
}

4.2.2 LocalParticleAbility

getPhotoLocalParticleAbility

// Just rewrite the registration method
private AbilityContext abilityContext;

@Override
public void register(AceAbility ability) {
    abilityContext = ability;
    LocalParticleAbility.super.register(ability);
}

MainAbility

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    getPhotoLocalParticleAbility.getInstance().register(this);
}

4.2.3 JS end

The JS side only needs to simply call the JAVA code related to the jump page.

Example:

makeAction(bundleName, abilityName, code, abilityType, data) {
    const action = {};
    action.bundleName = bundleName;
    action.abilityName = abilityName;
    action.messageCode = code;
    action.abilityType = abilityType;
    action.data = data;
    action.syncOption = 0;
    return action;
},
async testGetPhotoInternalAbility() {
    const action = this.makeAction('cn.crcrc.arkui_example',
        'cn.crcrc.arkui_example.ability.getPhotoInternalAbility', 1001, 1, {});
    const result = await FeatureAbility.callAbility(action);
    console.info(result)
    if(result != null){
        this.avatarURL = result
    }
},
testGetPhotoLocalParticleAbility(){
    this.javaInterface = createLocalParticleAbility('cn.crcrc.arkui_example.ability.getPhotoLocalParticleAbility');
    this.javaInterface.getPhotoUri().then(result => {
        console.info(result);
        if(result != null){
            this.avatarURL = result
        }
    }, error => {
        console.error('testGetPhotoLocalParticleAbility error');
    });
}

4.3 return value problem

\qquad AbilityContext. The result of startAbility can be obtained through the callback method onAbilityResult of Ability. Through the above analysis, it is obvious that since the startAbility is through MainAbility, onAbilityResult naturally needs to be rewritten in MainAbility to obtain the result.

\qquad At this time, it is necessary to solve the problem of passing the album return value to Internal Ability or LocalParticleAbility, and then returning the obtained picture address to the JS side.

\qquad Here I choose to spread messages through orderly public events. The basic idea is to publish events in the onAbilityResult of MainAbility, with the Uri value of photo album pictures; Subscribe to events in Internal Ability or LocalParticleAbility. After obtaining the Uri value, use the Utils class to store the image in the address accessible by the JS IMAGE component after customized processing.

MainAbility

/*
 * Publish orderly public events
 */
private void orderlyEventPublish(String data) {
    HiLog.info(LABEL, "Public event start");
    //1. Build an Intent object that contains the identifier of the custom event
    Intent intent = new Intent();
    Operation oper = new Intent.OperationBuilder().withAction("cn.crcrc.arkui_example.event") //Is the identification of a custom public event
            .build();
    intent.setOperation(oper);
    //2. Build CommonEventData object
    CommonEventData commonEventData = new CommonEventData(intent);

    //Only ordered public events can carry two special attributes and optional parameters, which are not necessary
    commonEventData.setData(data);
    commonEventData.setCode(1001);
    //Configure corresponding permissions for public events
    CommonEventPublishInfo publishInfo = new CommonEventPublishInfo();
    publishInfo.setOrdered(true);

    //3. Action of core release events, public events released, and orderly public events
    try {
        CommonEventManager.publishCommonEvent(commonEventData, publishInfo);
        HiLog.info(LABEL, "Publish orderly public events and complete");
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

/**
 * Jump callback
 */
@Override
protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
    super.onAbilityResult(requestCode, resultCode, resultData);
    try {
        System.out.println("APP LOG resultData: " + resultData.getUriString());
        // Deliver information by publishing orderly public events
        orderlyEventPublish(resultData.getUriString());
    } catch (Exception e) {
        orderlyEventPublish("fail");
        e.printStackTrace();
    }
}

Subscribe to events in internal capability or localparticlecapability, for example:

/**
 * Subscription event
 */
private void subscribeCommonEvent() {
    if(!isSubscribe){
        HiLog.info(LABEL,"Subscription start:");

        //1. Build MatchingSkills object
        MatchingSkills matchingSkills = new MatchingSkills();
        matchingSkills.addEvent("cn.crcrc.arkui_example.event"); //Subscribe to custom public events

        //2. Build subscription information object
        CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);

        //3. Build subscriber object
        subscriber = new CommonEventSubscriber(subscribeInfo){
            // Callback method
            @Override
            public void onReceiveEvent(CommonEventData commonEventData) {
                HiLog.info(LABEL,"Public event received");
                Boolean setImaDataResult = false;
                if(commonEventData.getData() != null)
                {
                    System.out.println("Data received:" + commonEventData.getData());
                    if(!commonEventData.getData().equals("fail"))
                    {
                        Uri uri = Uri.parse(commonEventData.getData());
                        long l = System.currentTimeMillis();
                        String fileName = String.valueOf(l);
                        setImaDataResult = utils.setImaData(uri,fileName);
                        photoUriString = "internal://app/" + String.valueOf(l) + ".jpg";
                        HiLog.info(LABEL,"js Access picture path:" + photoUriString);
                    }
                    latch.countDown();
                }else {
                    HiLog.info(LABEL,"A public event was received, but the data is empty");
                    System.out.println("APP LOG A public event was received, but the data is empty");
                }

                // Currently, onReceiveEvent can only be executed on the ui main thread
                // Time consuming tasks are sent to sub threads for asynchronous execution to ensure that ui threads are not blocked
                final AsyncCommonEventResult result = goAsyncCommonEvent();
                Boolean finalSetImaDataResult = setImaDataResult;
                handler.postTask(new Runnable() {
                    @Override
                    public void run() {
                        if(finalSetImaDataResult){
                            HiLog.info(LABEL,"Perform database operation");
                            // Stored in database
                        }
                        HiLog.info(LABEL,"Database operation completed");
                        result.finishCommonEvent();//end event
                    }
                });
            }
        };

        //4. Core actions of subscribing to public events
        try {
            CommonEventManager.subscribeCommonEvent(subscriber);
            isSubscribe = true;
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }else{
        HiLog.info(LABEL,"Public events cannot be subscribed repeatedly");
    }
}

You can put the subscription and unsubscribe actions into the registration and logout actions respectively, taking LocalParticleAbility as an example

@Override
public void register(AceAbility ability) {
    abilityContext = ability;
    LocalParticleAbility.super.register(ability);

    utils = new Utils(abilityContext);  // Get examples
    // Register public events
    subscribeCommonEvent();
}

@Override
public void deregister(AceAbility ability) {
    abilityContext = null;
    LocalParticleAbility.super.deregister(ability);

    // Unsubscribe
    try{
        CommonEventManager.unsubscribeCommonEvent(subscriber);
    }catch(RemoteException e) {
        HiLog.error(LABEL,"Exception occurred during unsubscribeCommonEvent invocation.");
    }
}

be careful:

  1. The JAVA side method called by JS will return before obtaining the picture address in the subscription event, resulting in the JS side obtaining a null value. Therefore, I adopt the CountDownLatch method to transfer data synchronously.
  2. When initializing subscription events, the callback method will be executed once, so judgment conditions should be added to prevent latch Error in countdown().
  3. The resultData of onAbilityResult is empty because the picture may not be selected and returned directly, so the resultData Geturistring should catch exception.

\qquad Among them, there should be other solutions to solve the JAVA side's early return value, such as using the CallBack method of LocalParticleAbility to open the thread and other image return values, and return them asynchronously. Or the JAVA side can store it first, and the JS side can call the JAVA PA related function to query the corresponding key value.

5, Debug log

6, Comparison and summary

\qquad When writing code, it is easy to realize that the local particleability method is more convenient than the Internal Ability, especially in the parameter transfer between JS and JAVA.

\qquad The LocalParticleAbility method is just like the JS side directly calling the method to realize the function, while the Internal Ability needs to judge which method to execute according to the passed code in onRemoteRequest.

\qquad This project can achieve the following effects by combining the following JAVA or JS side database functions:
 

The function implementation of image transfer in the project can be realized by viewing the corresponding uploaded files.

In fact, you need to select the corresponding IJs method of the image, but you can also select the corresponding IJs method of the image.

var str = {
    "want": {
        "type": "image/*",
        "action": "android.intent.action.GET_CONTENT",
        "flags": wantConstant.Flags.FLAG_NOT_OHOS_COMPONENT
    },
};
featureAbility.startAbilityForResult(str, (error, data) => {
    if (error) {
        console.error('Operation failed. Cause: ' + error);
        return;
    }
    console.info('Operation succeeded: ' + data);
});

If you have better ideas or methods for the function realization of this project, please give us your comments!

Topics: Java