为MnnChat实现手机公共存储目录加载模型

以下代码为简化思路,测试模为Qwen2-0.5B-Instruct-MNN,模型文件放在

/storage/emulated/0/modelscope/Qwen2-0.5B-Instruct-MNN

模型文件不要用git clone获取到的,必须是用魔搭、魔乐sdk获取到的,或者从之前从MNN下载缓存中拷贝过来的
测试时需要先获取管理所有文件的权限,然后清退并重新打开MNN,等待5秒后跳转ChatActivity.java并自动加载模型

更多mnnchat实现关注我的github
https://github.com/AIddlx/ddlx-MnnLlmChat-OpenaiAPI

此次实现的java文件在
ddlx-MnnLlmChat-OpenaiAPI

我的b站
https://space.bilibili.com/3493283904883140

以下是此次的代码

以下是app\src\main\AndroidManifest.xml需要新增的权限

       <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

以下是MainActivity的代码
app\src\main\java\com\alibaba\mnnllm\android\MainActivity.java

// Created by ruoyi.sjd on 2024/12/25.
// Copyright (c) 2024 Alibaba Group Holding Limited All rights reserved.

package com.alibaba.mnnllm.android;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;

import com.alibaba.mls.api.download.ModelDownloadManager;
import com.alibaba.mnnllm.android.chat.ChatActivity;
import com.alibaba.mnnllm.android.history.ChatHistoryFragment;
import com.alibaba.mnnllm.android.modelist.ModelListFragment;
import com.alibaba.mnnllm.android.settings.MainSettings;
import com.alibaba.mnnllm.android.update.UpdateChecker;
import com.alibaba.mnnllm.android.utils.GithubUtils;
import com.alibaba.mnnllm.android.utils.ModelUtils;
import com.techiness.progressdialoglibrary.ProgressDialog;

import java.io.File;
import java.util.Objects;

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainActivity";
    private ProgressDialog progressDialog;
    private DrawerLayout drawerLayout;
    private ActionBarDrawerToggle toggle;
    private ModelListFragment modelListFragment;
    private ChatHistoryFragment chatHistoryFragment;
    private UpdateChecker updateChecker;


    /**
     * 用于启动权限请求的 ActivityResultLauncher
     * 实现日期:2025年4月21日 创作者:ddlx
     */
    private ActivityResultLauncher<String> requestPermissionLauncher;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        drawerLayout = findViewById(R.id.drawer_layout);
        updateChecker = new UpdateChecker(this);
        updateChecker.checkForUpdates(this, false);

        // Set up ActionBar toggle
        toggle = new ActionBarDrawerToggle(
                this, drawerLayout,
                toolbar,
                R.string.nav_open,
                R.string.nav_close);
        drawerLayout.addDrawerListener(toggle);
        toggle.syncState();
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.main_fragment_container,
                        getModelListFragment())
                .commit();
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.history_fragment_container,
                        getChatHistoryFragment())
                .commit();
        getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {
                if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
                    drawerLayout.closeDrawer(GravityCompat.START);
                } else {
                    finish();
                }
            }
        });
        setSupportActionBar(toolbar);
        Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
 
        // 初始化权限请求启动器
        //实现日期:2025年4月21日 创作者:ddlx
        initializePermissionLauncher();

        // 如果需要,请求存储权限
        //实现日期:2025年4月21日 创作者:ddlx
        requestStoragePermissionIfNeeded();

        // 延迟并运行模型
        // 实现日期:2025年4月21日 创作者:ddlx
        delayAndRunModel();
    }


    private Fragment getModelListFragment() {
        if (modelListFragment == null) {
            modelListFragment = new ModelListFragment();
        }
        return modelListFragment;
    }

    private Fragment getChatHistoryFragment() {
        if (chatHistoryFragment == null) {
            chatHistoryFragment = new ChatHistoryFragment();
        }
        return chatHistoryFragment;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (toggle.onOptionsItemSelected(item)) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }



    /**
     * 初始化权限请求发射器
     *
     * 使用 registerForActivityResult 方法注册一个新的 ActivityResultContracts.RequestPermission 类型的发射器。
     * 当权限请求的结果返回时,会调用回调函数。如果权限未被授予,则显示一个 Toast 提示用户需要存储权限。
     * 实现日期:2025年4月21日 创作者:ddlx
     */
    private void initializePermissionLauncher() {
        requestPermissionLauncher = registerForActivityResult(
                new ActivityResultContracts.RequestPermission(),
                isGranted -> {
                    if (!isGranted) {
                        Toast.makeText(this, "需要存储权限", Toast.LENGTH_LONG).show();
                    }
                }
        );
    }

    /**
     * 根据系统版本请求存储权限
     *
     * @return 无返回值
     * 实现日期:2025年4月21日 创作者:ddlx
     */
    private void requestStoragePermissionIfNeeded() {
        // 检查是否已经拥有管理外部存储的权限
        if (Environment.isExternalStorageManager()) {
            // 如果已经有权限,则记录日志并返回
            Log.d(TAG, "已拥有 MANAGE_EXTERNAL_STORAGE 权限。");
            return;
        }

        // 检查 Android 版本是否为 R (API 30) 或更高
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
            // Android R 及以上版本,请求 MANAGE_EXTERNAL_STORAGE 权限
            Log.d(TAG, "为 Android R+ 请求 MANAGE_EXTERNAL_STORAGE 权限");
            try {
                // 创建意图以请求所有文件访问权限
                Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivity(intent);
            } catch (Exception e) {
                // 如果启动 Activity 出错,记录错误日志
                Log.e(TAG, "启动 MANAGE_ALL_FILES_ACCESS_PERMISSION activity 时出错", e);
                // 尝试再次启动,不指定特定包
                Intent intent = new Intent();
                intent.setAction(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivity(intent); // Try again without specific package
            }
        } else {
            // Android R 以下版本,请求传统的存储权限
            // 检查 Android 版本是否为 Android 14 (API 34) 或更高
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                // Android 14 及以上版本,请求 READ_MEDIA_IMAGES 权限
                Log.d(TAG, "为 Android 14+ 请求 READ_MEDIA_IMAGES 权限");
                requestPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES);
            } else {
                // Android 14 以下版本,请求 READ_EXTERNAL_STORAGE 权限
                Log.d(TAG, "为低于 Android 14 的版本请求 READ_EXTERNAL_STORAGE 权限");
                requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
            }
        }
    }
    /**
     * 延迟5秒后运行模型
     *
     * 该方法会创建一个 Handler 实例,并设置一个延迟5秒的 Runnable,当延迟时间到达后,将执行 runModel 方法。
     * 实现日期:2025年4月21日 创作者:ddlx
     */
    private void delayAndRunModel() {
        // 创建一个 Handler 实例
        Handler handler = new Handler(Looper.getMainLooper());
        // 延迟5秒后执行 runModel 方法
        handler.postDelayed(() -> runModel(null, null, null), 5000); // 5000 毫秒 = 5 秒
    }

    /**
     * 运行模型
     *
     * @param destModelDir 目标模型目录
     * @param modelId        模型ID
     * @param sessionId    会话ID
     * 实现日期:2025年4月21日 创作者:ddlx
     */
    public void runModel(String destModelDir, String modelId, String sessionId) {
        // 固定模型路径和 modelId
        String absolutePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/modelscope/Qwen2-0.5B-Instruct-MNN";
        modelId = "taobao-mnn/Qwen2-0.5B-Instruct-MNN";
        destModelDir = absolutePath; // 使用固定路径


        Log.d(TAG, "运行模型 destModelDir: " + destModelDir);
        if (MainSettings.INSTANCE.isStopDownloadOnChatEnabled(this)) {
            ModelDownloadManager.getInstance(this).pauseAllDownloads();
        }
        drawerLayout.close();
        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage(getResources().getString(R.string.model_loading));
        progressDialog.show();

        // 移除扩散模型判断逻辑
        String configFileName = "config.json";
        String configFilePath = destModelDir + "/" + configFileName;
        boolean configFileExists = new File(configFilePath).exists();
        if (!configFileExists) {
            Toast.makeText(this, getString(R.string.config_file_not_found, configFilePath), Toast.LENGTH_LONG).show();
            progressDialog.dismiss();
            return;
        }

        progressDialog.dismiss();
        Intent intent = new Intent(this, ChatActivity.class);
        intent.putExtra("chatSessionId", sessionId);
        // 移除扩散模型相关的额外参数设置
        intent.putExtra("configFilePath", configFilePath);
        intent.putExtra("modelId", modelId);
        intent.putExtra("modelName", "Qwen2-0.5B-Instruct-MNN");


        startActivity(intent);
    }

    public void onStarProject(View view) {
        GithubUtils.starProject(this);
    }

    public void onReportIssue(View view) {
        GithubUtils.reportIssue(this);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == ModelDownloadManager.REQUEST_CODE_POST_NOTIFICATIONS) {
            ModelDownloadManager.getInstance(this).startForegroundService();
        }
    }

    public void checkForUpdate() {
        updateChecker.checkForUpdates(this, true);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值