tencent cloud

All product documents
Tencent Cloud EdgeOne
DocumentationTencent Cloud EdgeOnePractical TutorialAPK Dynamic PackagingEdgeOne facilitate APK dynamic packaging of AndroidStep 2: Write the Channel Information into the APK Package with EdgeOne Edge Functions
Step 2: Write the Channel Information into the APK Package with EdgeOne Edge Functions
Last updated: 2025-04-25 10:50:08
Step 2: Write the Channel Information into the APK Package with EdgeOne Edge Functions
Last updated: 2025-04-25 10:50:08
Through EdgeOne edge function, we can dynamically write channel information into the APK package. Users only need to access the domain bound to the edge function and trigger the appropriate configuration to enable the edge function, achieving dynamic packaging and accelerated distribution of the APK.

Step 1: Add an Acceleration Domain Name for Enhanced Distribution Speeds

Please follow the instructions in Adding A Domain Name for Acceleration to add an acceleration domain, for example: www.example.com, and configure the origin server to the COS where the Android APK parent package is located, as shown below:
Note:
This domain will be used to access and download the APK installation package.


Step 2: Create an Edge Function for Triggering Channel Information Writing

1. Follow the instructions in Function Management to create an edge function and copy the following code into the function code.
const CUSTOM_BLOCK_VALUE_LENGTH = 10240;
const APK_SIGNING_BLOCK_MAGIC_LENGTH = 16;
const APK_SIGNING_BLOCK_OFFSET_LENGTH = 8;

const APK_COMMENT_LENGTH = 512;

class EdgePack {
totalSize;
signVersion;
centralDirectoryOffset;
customBlockValueStart;
customBlockValueEnd;
rangeRelativeOffset;
customInfo;

constructor() {
this.totalSize = null;
this.signVersion = null;
this.centralDirectoryOffset = null;
this.customBlockValueStart = null;
this.customBlockValueEnd = null;
this.rangeRelativeOffset = null;
this.customInfo = null;
}

async handle(event) {
const { request } = event;

const headers = new Headers(request.headers);

const modifiedRequest = new Request(request, { headers });

if (!this.checkRequest(modifiedRequest)) {
return;
}

let response = null;
try {
const headRequest = new Request(modifiedRequest.url, {
method: 'HEAD',
headers: modifiedRequest.headers,
});
response = await fetch(headRequest);
} catch (err) {
const error = {
code: 'FETCH_ORIGIN_ERROR',
message: err?.message,
};
response = new Response(JSON.stringify(error), {
status: 590,
});
}

if (!this.checkResponse(response)) {
return event.respondWith(response);
}

response.headers.set('Cache-Control', 'max-age=0');

const streamResponse = new Response(
await this.combineStreams(modifiedRequest),
response
);

event.respondWith(streamResponse);
}

getRelativeOffset(response) {
const start = this.customBlockValueStart;
const end = this.customBlockValueEnd;

const range = response.headers.get('Content-Range');

if (!range) return start;

const match = range.match(/bytes\\s*(\\d*)-(\\d*)/i);
if (!match || match?.length < 2) {
return start;
}

if (+match[2] < start || +match[1] > end) {
return null;
}

return start - +match[1];
}

checkRequest(request) {
if (request.method !== 'GET') {
return false;
}

if (request.headers.has('Range')) {
return false;
}

const { pathname, searchParams } = new URL(request.url);

const comment = searchParams?.get('comment');

if (!pathname.endsWith('.apk') || !comment) {
return false;
}

this.customInfo = comment;
return true;
}

checkResponse(response) {
if (response.status !== 200 && response.status !== 206) {
return false;
}

const contentLength = response.headers.get('Content-Length');

if (response.body === null || contentLength === null) {
return false;
}

this.totalSize = Number(contentLength);

const cosOffsetHeader = response.headers.get('x-cos-meta-edgepack-offset');
const cosTypeHeader = response.headers.get('x-cos-meta-edgepack-type');

if (!cosOffsetHeader || !cosTypeHeader) {
return false;
}

this.signVersion = cosTypeHeader;
this.centralDirectoryOffset = Number(cosOffsetHeader);

if (this.signVersion === 'v1') {
this.customBlockValueStart = this.totalSize - APK_COMMENT_LENGTH;
this.customBlockValueEnd = this.totalSize;
} else {
this.customBlockValueStart =
this.centralDirectoryOffset -
CUSTOM_BLOCK_VALUE_LENGTH -
APK_SIGNING_BLOCK_MAGIC_LENGTH -
APK_SIGNING_BLOCK_OFFSET_LENGTH;
this.customBlockValueEnd = this.centralDirectoryOffset;
}

this.rangeRelativeOffset = this.getRelativeOffset(response);

if (this.rangeRelativeOffset === null) {
return false;
}

return true;
}

async combineStreams(request) {
const { readable, writable } = new TransformStream();
this.handleStream(request, writable);
return readable;
}

async handleStream(request, writable) {
const comment = this.customInfo;
const relativeOffset = this.rangeRelativeOffset;

const encoder = new TextEncoder();
const section = encoder.encode(comment);

try {
const apkHeader = await this.apkHeaderStream(request);

try {
await apkHeader.pipeTo(writable, {
preventClose: true,
});
} catch (e) {
console.error('HEADER_STREAM_ERROR: ', e);
}

// Return to Blob data
const apkBody = await this.apkBodyStream(
request,
section,
relativeOffset
);

const apkBodyStream = apkBody.stream();

try {
await apkBodyStream.pipeTo(writable, {
preventClose: true,
});
} catch (e) {
console.error('BODY_STREAM_ERROR: ', e);
}

const apkTail = await this.apkTailStream(request);

try {
await apkTail.pipeTo(writable, {
preventClose: true,
});
} catch (e) {
console.error('TAIL_STREAM_ERROR: ', e);
}
} catch (err) {
console.error('HANDLE_STREAM_ERROR: ', err);
} finally {
let writer = writable.getWriter();
writer.close();
writer.releaseLock();
}
}

async apkHeaderStream(request) {
const headers = new Headers(request.headers);
headers.set('Range', `bytes=0-${this.customBlockValueStart - 1}`);

//Obtaining the part before the signature block.
const headResponse = await fetch(request, {
headers: headers,
});

return headResponse.body;
}

async apkBodyStream(request, section = null, relativeOffset = 0) {
const headers = new Headers(request.headers);
headers.set(
'Range',
`bytes=${this.customBlockValueStart}-${this.customBlockValueEnd - 1}`
);

const middleResponse = await fetch(request, {
headers: headers,
});

const reader = middleResponse.body.getReader();

let outputBuffers = [];
try {
let handledBytes = this.customBlockValueStart;
while (true) {
const result = await reader.read();

if (result.done) {
console.log('APK_BODY_STREAM_DONE');
break;
}

const startByteOffset = handledBytes;
const buffer = result.value;
handledBytes += buffer.byteLength;

const min = Math.max(startByteOffset, relativeOffset);
const max = Math.min(relativeOffset + section.byteLength, handledBytes);

if (min < max) {
const bufferStart = min - startByteOffset;
const sectionStart = min - relativeOffset;
const sectionEnd = max - relativeOffset;

const replacement = section.subarray(sectionStart, sectionEnd);

new Uint8Array(buffer).set(replacement, bufferStart);
}

outputBuffers.push(buffer);
}
} catch (err) {
console.error('APK_BODY_STREAM_ERROR: ', err);
}
return new Blob(outputBuffers);
}

async apkTailStream(request) {
const headers = new Headers(request.headers);
headers.set(
'Range',
`bytes=${this.customBlockValueEnd}-${this.totalSize - 1}`
);

const tailResponse = await fetch(request, {
headers: headers,
});

return tailResponse.body;
}
}

async function handleEvent(event) {
const edgepack = new EdgePack();
await edgepack.handle(event);
}

addEventListener('fetch', handleEvent);
2. After deploying the function, configure the trigger rule under Function Management as directed, where the HOST value is the acceleration domain name created in Step 1, as shown below:

3. Click OK to complete the creation of the trigger rule. When users access the domain www.example.com with a file suffix of .apk, it will trigger the edge function for dynamic packaging.


Was this page helpful?
You can also Contact Sales or Submit a Ticket for help.
Yes
No

Feedback

Contact Us

Contact our sales team or business advisors to help your business.

Technical Support

Open a ticket if you're looking for further assistance. Our Ticket is 7x24 available.

7x24 Phone Support
Hong Kong, China
+852 800 906 020 (Toll Free)
United States
+1 844 606 0804 (Toll Free)
United Kingdom
+44 808 196 4551 (Toll Free)
Canada
+1 888 605 7930 (Toll Free)
Australia
+61 1300 986 386 (Toll Free)
EdgeOne hotline
+852 300 80699
More local hotlines coming soon