简介

这是一个运用Java(以后还会推出Kotlin版别)语言,从0开发一个Android渠道,挨近企业级的项目(我的云音乐),包括了根底内容,高档内容,项目封装,项appetite目重构等知识;主要是运用体系功用,流行的第三方结构,第三方服务,完结挨近企业级商业级项目。

功用点

隐私协议对话框 发动json格式怎么打开界面和动态处理权限 引导界面和广告 轮宫颈癌播图和侧滑菜单 主页杂乱列表和列表排序 音乐播映和音乐列表管理 大局音乐操控条 桌面歌词和自定义款式 大局媒体操控中心 谈论和回复谈论 谈论富文本点击 谈论提醒人和论题 朋json解析友圈动态列表公积金和发布 高德地图定位和途径规划 阿里云OSS上传二维码图案 视频播映和操控 QQ/微信登录和分享 商城/购物车微信付出宝付出 文本和图appointment片谈天 音讯离线推送 主动和手动检查更新 内存泄漏和优化 …

开发环境概述

2022年5月开发完结的,所以全部都是最新的,均匀每3年会从头制造,现在已经是第三版Go了。

JDK17
Android 12/13
最低兼容版别:Android 6.0
Android Studio 2021.1

编译和运行

用最新AS翻开MyCloudMusicAndroidJava目录,然后等候彻底编译成功,由于是企业级项目,所以第三方依靠许多,同时代码量也许多,所以必需求确认彻底编译成功,才干运行。

项目目录结approach

├── MyCloudMusicAndroidJava
│   ├── LRecyclerview //第三方Recyclerview结构
│   ├── LetterIndexView //相似微信通讯录字母索引
│   ├── app //云音乐项目
│   ├── build.gradle
│   ├── common.gradle //通用项目装备文件
│   ├── config //装备目录,例如签名
│   ├── glidepalette //Glide画板,用来从网络图片提取色彩
│   ├── gradle
│   ├── gradle.properties
│   ├── gradlew
│   ├── gradlew.bat
│   ├── keystore.properties
│   ├── local.properties
│   ├── settings.gradle
│   ├── super-j //公用Java语言扩展
│   ├── super-player-tencent //腾讯开源的超级播映器
│   ├── super-speech-baidu //百度语音辨认

依靠结构

内容太多,只列出部分。

//分页组件版别
//这儿能够检查最新版别:https://developer.android.google.cn/jetpack/androidx/releases/paging
def paging_version = "3.1.1"
//增加所有libs目录里边的jar,aar
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
//官方兼容组件,像AppCompatActivity便是该依靠里边的
implementation 'androidx.appcompat:appcompat:1.4.1'
//Material Design组件,像FloatingActionButton便是该依靠里边的
implementation 'com.google.android.material:material:1.4.0'
//官方供给的约束布局,像ConstraintLayout便是该依靠里边的
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
//UI结构,主要是用他的东西类,也能够单独拷贝出来
//https://qmuiteam.com/android/get-started
implementation 'com.qmuiteam:qmui:2.0.1'
//动态处理权限
//https://github.com/permissions-dispatcher/PermissionsDispatcher
implementation "com.github.permissions-dispatcher:permissionsdispatcher:4.8.0"
annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:4.8.0"
//api:依靠会传递到其他运用本模块的项目
implementation project(path: ':super-j')
...
//运用gson解析json
//https://github.com/google/gson
implementation 'com.google.code.gson:gson:2.9.0'
//主动开释RxJava相关资源
//https://github.com/uber/AutoDispose
implementation "com.uber.autodispose2:autodispose-androidx-lifecycle:2.1.1"
//banner轮播图结构
//https://github.com/youth5201314/banner
implementation 'io.github.youth5201314:banner:2.2.2'
//图片加载结构,还引证他意图是,coil有些功用欠好完成
//https://github.com/bumptech/glide
implementation 'com.github.bumptech.glide:glide:+'
annotationProcessor 'com.github.bumptech.glide:compiler:+'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
//给控件增加未读音讯数红点
//https://github.com/bingoogolapple/BGABadgeView-Android
implementation 'com.github.bingoogolapple.BGABadgeView-Android:api:1.2.0'
annotationProcessor 'com.github.bingoogolapple.BGABadgeView-Android:compiler:1.2.0'
//webview进展条
//https://github.com/youlookwhat/WebProgress
implementation 'com.github.youlookwhat:WebProgress:1.2.0'
//日志结构
//https://github.com/JakeWharton/timber
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation "androidx.media:media:+"
//和Glide合作处理图片
//能够完成许多作用
//含糊;圆角;圆
//咱们这儿是用它完成含糊作用
//https://github.com/wasabeef/glide-transformations
implementation 'jp.wasabeef:glide-transformations:+'
//圆形图片控件
//https://github.com/hdodenhof/CircleImageView
implementation 'de.hdodenhof:circleimageview:+'
//下载结构
//https://github.com/ixuea/android-downloader
implementation 'com.ixuea:android-downloader:3.0.0'
//阿里云oss
//官方文档:https://help.aliyun.com/document_detail/32043.html
//sdk地址:https://github.com/aliyun/aliyun-oss-android-sdk
implementation 'com.aliyun.dpa:oss-android-sdk:+'
//高德地图,这儿引证的是3d
//https://lbs.amap.com/api/android-sdk/guide/create-project/android-studio-create-project#gradle_sdk
implementation 'com.amap.api:3dmap:+'
//定位功用
implementation 'com.amap.api:location:+'
//百度语音相关技术,现在主要用在收货地址修改界面,语音输入收货地址
//https://ai.baidu.com/ai-doc/SPEECH/Pkgt4wwdx#%E9%9B%86%E6%88%90%E6%8C%87%E5%8D%97
implementation project(path: ':super-speech-baidu')
//TextView显现富文本,现在主要用在产品概况界面,显现富文本产品描述
//https://github.com/wangchenyan/html-text
implementation 'com.github.wangchenyan:html-text:+'
//Hutool是一个小而全的Java东西类库
// 经过静态办法封装,下降相关API的学习本钱
// 提高工作效率,使Java拥有函数式语言般的优雅
//https://github.com/looly/hutool
implementation 'cn.hutool:hutool-all:5.7.14'
//付出宝付出
//https://opendocs.alipay.com/open/204/105296
implementation 'com.alipay.sdk:alipaysdk-android:+@aar'
//融云IM
//https://docs.rongcloud.cn/v4/5X/views/im/ui/guide/quick/include/android.html
implementation 'cn.rongcloud.sdk:im_lib:+'
//微信付出
//官方sdk下载文档:https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html
//官方集成文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5
implementation 'com.tencent.mm.opensdk:wechat-sdk-android:+'
//内存泄漏检测东西
//https://github.com/square/leakcanary
//只有调试形式下才增加该依靠
debugImplementation 'com.squareup.leakcanary:leakcanary-android:+'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

用户协议对话框

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

运用自定义DialogFragment完成,内容是放到字符串文件中的,其中的链接是HTML标签,设置后就能够点击艺术签名设计了,然后修正默许对话框宽度,由于默许的有点窄。

public class TermServiceDialogFragment extends BaseViewModelDialogFragment<FragmentDialogTermServiceBinding> {
    ...
    @Override
    protected void initViews() {
        super.initViews();
        //点击弹窗外边不能封闭
        setCancelable(false);
        SuperTextUtil.setLinkColor(binding.content, getActivity().getColor(R.color.link));
    }
    @Override
    protected void initListeners() {
        super.initListeners();
        binding.primary.setOnClickListener(view -> {
            dismiss();
            onAgreementClickListener.onClick(view);
        });
        binding.disagree.setOnClickListener(view -> {
            dismiss();
            SuperProcessUtil.killApp();
        });
    }
    @Override
    public void onResume() {
        super.onResume();
        //修正宽度,默许比AlertDialog.Builder显现对话框宽度窄,看着欠好看
        //参阅:https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height
        ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
        params.width = (int) (ScreenUtil.getScreenWith(getContext()) * 0.9);
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
    }
}

动态权限

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

高版别必需求动态处理权限,这儿在发动界面恳求了一些权限,但引荐在用到的时分才获取,写法差不多,这儿运用第三方结构完成,当然也能够直接运用体系API完成。

/**
 * 权限授权了就会调用该办法
 * 恳求相机权限意图是扫描二维码,摄影
 */
@NeedsPermission({
        Manifest.permission.CAMERA,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION
})
void onPermissionGranted() {
    //假如有权限就进入下一步
    prepareNext();
}
/**
 * 显现权限授权对话框
 * 意图是提示用户
 */
@OnShowRationale({
        Manifest.permission.CAMERA,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION
})
void showRequestPermission(PermissionRequest request) {
    new AlertDialog.Builder(getHostActivity())
            .setMessage(R.string.permission_hint)
            .setPositiveButton(R.string.allow, (dialog, which) -> request.proceed())
            .setNegativeButton(R.string.deny, (dialog, which) -> request.cancel()).show();
}
/**
 * 拒绝了权限调用
 */
@OnPermissionDenied({
        Manifest.permission.CAMERA,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION
})
void showDenied() {
    //退出运用
    finish();
}
/**
 * 再次获取权限的提示
 */
@OnNeverAskAgain({
        Manifest.permission.CAMERA,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION
})
void showNeverAsk() {
    //持续恳求权限
    checkPermission();
}
/**
 * 授权后回调
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    //将授权成果传递到结构
    SplashActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}

引导界面

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
引导界面比较简略,便是多个图片能够左右翻滚,全体运用ViewPager+Fragment完成,也能够运用ViewPagejson怎么读r2,后边有讲解。

/**
 * 引导界面适配器
 */
public class GuideAdapter extends BaseFragmentStatePagerAdapter<Integer> {
    /***
     *  @param context 上下文
     * @param fm Fragment管理器
     */
    public GuideAdapter(Context context, @NonNull FragmentManager fm) {
        super(context, fm);
    }
    /**
     * 回来当时方位Fragment
     *
     * @param position
     * @return
     */
    @NonNull
    @Override
    public Fragment getItem(int position) {
        return GuideFragment.newInstance(getData(position));
    }
}
/**
 * 引导界面Fragment
 */
public class GuideFragment extends BaseViewModelFragment<FragmentGuideBinding> {
    ...
    @Override
    protected void initDatum() {
        super.initDatum();
        int data = getArguments().getInt(Constant.ID);
        binding.icon.setImageResource(data);
    }
}

广告界面

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

完成图片广告和视频广告,广告数据是在主页是缓存到本地,意图是在发动界面加载更快,由于实在项目中,大部分项目发动页面广告时刻总共就5秒,假如龚俊太长了approve用户体会欠好json数据,假如是从网络恳求,那么网络或许就耗时2秒左右,所以导致就美哟多少时刻显现广告了。

下载广告

private void downloadAd(Ad data) {
    if (SuperNetworkUtil.isWifiConnected(getHostActivity())) {
        //wifi才下载
        sp.setSplashAd(data);
        //判别文件是否存在,假如存在就不下载
        File targetFile = FileUtil.adFile(getHostActivity(), data.getIcon());
        if (targetFile.exists()) {
            return;
        }
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //FutureTarget会堵塞
                            //所以需求在子线程调用
                            FutureTarget<File> target = Glide.with(getHostActivity().getApplicationContext())
                                    .asFile()
                                    .load(ResourceUtil.resourceUri(data.getIcon()))
                                    .submit();
                            //获取下载的文件
                            File file = target.get();
                            //将文件拷贝到咱们需求的方位
                            FileUtils.moveFile(file, targetFile);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
        ).start();
    }
}

显现广告

/**
 * 显现视频广告
 *
 * @param data
 */
private void showVideoAd(File data) {
    SuperViewUtil.show(binding.video);
    SuperViewUtil.show(binding.preload);
    //在要用到的时分在初始化,更节省资源,当然播映器控件也能够在这儿动态创立
    //设置播映监听器
    //创立 player 目标
    player = new TXVodPlayer(getHostActivity());
    //静音,当然也能够在界面上增加静音切换按钮
    player.setMute(true);
    //要害 player 目标与界面 view
    player.setPlayerView(binding.video);
    //设置播映监听器
    player.setVodListener(this);
    //铺满
    binding.video.setRenderMode(TXLiveConstants.RENDER_MODE_FULL_FILL_SCREEN);
    //敞开硬件加速
    player.enableHardwareDecode(true);
    player.startPlay(data.getAbsolutePath());
}

显现图片便是显现本地图片了,没什么难点,就不贴代码了。

主页/歌单概况/黑胶唱片界面

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

主页没有顶部是轮播图json,然后是能够左右的菜单,接下来json怎么读是热门歌单,引荐单曲,终究是主页排序模块;全体上运用RecycerView完成,轮播图:

Banner bannerView = holder.getView(R.id.banner);
BannerImageAdapter<Ad> bannerImageAdapter = new BannerImageAdapter<Ad>(data.getData()) {
    @Override
    public void onBindView(BannerImageHolder holder, Ad data, int position, int size) {
        ImageUtil.show(getContext(), (ImageView) holder.itemView, data.getIcon());
    }
};
bannerView.setAdapter(bannerImageAdapter);
bannerView.setOnBannerListener(onBannerListener);
bannerView.setBannerRound(DensityUtil.dip2px(getContext(), 10));
//增加生命周期观察者
bannerView.addBannerLifecycleObserver(fragment);
bannerView.setIndicator(new CircleIndicator(getContext()));

引荐歌单

//设置标题,将标题放到每个具体的item上,优点是便利全体排序
holder.setText(R.id.title, R.string.recommend_sheet);
//显现更多容器
holder.setVisible(R.id.more, true);
holder.getView(R.id.more).setOnClickListener(v -> {
});
RecyclerView listView = holder.getView(R.id.list);
if (listView.getAdapter() == null) {
    //设置显现3列
    GridLayoutManager layoutManager = new GridLayoutManager(listView.getContext(), 3);
    listView.setLayoutManager(layoutManager);
    sheetAdapter = new SheetAdapter(R.layout.item_sheet);
    //item点击
    sheetAdapter.setOnItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) {
            if (discoveryAdapterListener != null) {
                discoveryAdapterListener.onSheetClick((Sheet) adapter.getItem(position));
            }
        }
    });
    listView.setAdapter(sheetAdapter);
    GridDividerItemDecoration itemDecoration = new GridDividerItemDecoration(getContext(), (int) DensityUtil.dip2px(getContext(), 5F));
    listView.addItemDecoration(itemDecoration);
}
sheetAdapter.setNewInstance(data.getData());

歌单概况

顶部是歌单二维码防伪信息,经过header完成艺术漆,底部是列表,显现歌单内容的音乐,点击音乐进入黑胶唱片播映界二维码扫描面。

//增加头部
adapter.addHeaderView(createHeaderView());
/**
 * 显现数据的办法
 *
 * @param holder
 * @param data
 */
@Override
protected void convert(@NonNull BaseViewHolder holder, Song data) {
    //显现方位
    holder.setText(R.id.index, String.valueOf(holder.getLayoutPosition() + offset));
    //显现标题
    holder.setText(R.id.title, data.getTitle());
    //显现信息
    holder.setText(R.id.info, data.getSinger().getNickname());
    if (offset != 0) {
        holder.setImageResource(R.id.more, R.drawable.close);
        holder.getView(R.id.more)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        SuperDialog.newInstance(fragmentManager)
                                .setTitleRes(R.string.confirm_delete)
                                .setOnClickListener(new View.OnClickListener() {
                                    @Override
                                    public void onClick(View v) {
                                        //查询下载使命
                                        DownloadInfo downloadInfo = AppContext.getInstance().getDownloadManager().getDownloadById(data.getId());
                                        if (downloadInfo != null) {
                                            //从下载结构删去
                                            AppContext.getInstance().getDownloadManager().remove(downloadInfo);
                                        } else {
                                            AppContext.getInstance().getOrm().deleteSong(data);
                                        }
                                        //从适配器中删去
                                        removeAt(holder.getAdapterPosition());
                                    }
                                }).show();
                    }
                });
    } else {
        //是否下载
        DownloadInfo downloadInfo = AppContext.getInstance().getDownloadManager().getDownloadById(data.getId());
        if (downloadInfo != null && downloadInfo.getStatus() == DownloadInfo.STATUS_COMPLETED) {
            //下载完结了
            //显现下载完结了图标
            holder.setGone(R.id.download, false);
        } else {
            holder.setGone(R.id.download, true);
        }
    }
    //处理修改状况
    if (isEditing()) {
        holder.setVisible(R.id.index, false);
        holder.setVisible(R.id.check, true);
        holder.setVisible(R.id.more, false);
        if (isSelected(holder.getLayoutPosition())) {
            holder.setImageResource(R.id.check, R.drawable.ic_checkbox_selected);
        } else {
            holder.setImageResource(R.id.check, R.drawable.ic_checkbox);
        }
    } else {
        holder.setVisible(R.id.index, true);
        holder.setVisible(R.id.check, false);
        holder.setVisible(R.id.more, true);
    }
}

黑胶工龄越长退休金越多吗唱片

上面是黑胶唱片,和网易云音乐差不多,随龚俊着音乐翻滚或暂停,顶部是操控相关,音乐播映逻辑是封装到MusicPlay艺术签名erManager中:

/**
 * 播映管理器默许完成
 */
public class MusicPlayerManagerImpl implements MusicPlayerManager, MediaPlayer.OnCompletionListener, AudioManager.OnAudioFocusChangeListener {
    ...
    /**
     * 获取播映管理器
     * getInstance:办法名能够随意取
     * 只是在Java这边大部分项目都取这个姓名
     *
     * @return
     */
    public synchronized static MusicPlayerManager getInstance(Context context) {
        if (instance == null) {
            instance = new MusicPlayerManagerImpl(context);
        }
        return instance;
    }
    @Override
    public void play(String uri, Song data) {
        //保存信息
        this.uri = uri;
        this.data = data;
        //开释播映器
        player.reset();
        //获取音频焦点
        if (!requestAudioFocus()) {
            return;
        }
        playNow();
    }
    private void playNow() {
        isPrepare = true;
        try {
            if (uri.startsWith("content://")) {
                //内容供给者格式
                //本地音乐
                //uri示例:content://media/external/audio/media/23
                player.setDataSource(context, Uri.parse(uri));
            } else {
                //设置数据源
                player.setDataSource(uri);
            }
            //同步准备
            //实在项目中或许会运用异步
            //由于假如网络欠好
            //同步或许会卡住
            player.prepare();
//            player.prepareAsync();
            //开端播映器
            player.start();
            //回调监听器
            publishPlayingStatus();
            //发动播映进展告诉
            startPublishProgress();
            prepareLyric(data);
        } catch (IOException e) {
            //TODO 播映过错处理
        }
    }
    @Override
    public void pause() {
        if (isPlaying()) {
            //假如在播映就暂停
            player.pause();
            ListUtil.eachListener(listeners, musicPlayerListener -> musicPlayerListener.onPaused(data));
            stopPublishProgress();
        }
    }
    @Override
    public void resume() {
        if (!isPlaying()) {
            //获取音频焦点
            if (!requestAudioFocus()) {
                return;
            }
            resumeNow();
        }
    }
    private void resumeNow() {
        //假如没有播映就播映
        player.start();
        //回调监听器
        publishPlayingStatus();
        //发动进展告诉
        startPublishProgress();
    }
    @Override
    public void addMusicPlayerListener(MusicPlayerListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
        //发动进展告诉
        startPublishProgress();
    }
    @Override
    public void removeMusicPlayerListener(MusicPlayerListener listener) {
        listeners.remove(listener);
    }
    @Override
    public void seekTo(int progress) {
        player.seekTo(progress);
    }
    /**
     * 发布播映中状况
     */
    private void publishPlayingStatus() {
//        for (MusicPlayerListener listener : listeners) {
//            listener.onPlaying(data);
//        }
        //运用重构后的办法
        ListUtil.eachListener(listeners, musicPlayerListener -> musicPlayerListener.onPlaying(data));
    }
    /**
     * 播映完毕了回调
     *
     * @param mp
     */
    @Override
    public void onCompletion(MediaPlayer mp) {
        isPrepare = false;
        //回调监听器
        ListUtil.eachListener(listeners, listener -> listener.onCompletion(mp));
    }
    @Override
    public void setLooping(boolean looping) {
        player.setLooping(looping);
    }
    /**
     * 音频焦点改变了回调
     *
     * @param focusChange
     */
    @Override
    public void onAudioFocusChange(int focusChange) {
        Timber.d("onAudioFocusChange %s", focusChange);
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                //获取到焦点了
                if (resumeOnFocusGain) {
                    if (isPrepare) {
                        resumeNow();
                    } else {
                        playNow();
                    }
                    resumeOnFocusGain = false;
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                //永久失掉焦点,例如:其他运用恳求时,也是播映音乐
                if (isPlaying()) {
                    pause();
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                //暂时性失掉焦点,例如:通话了,或许呼叫了语音帮手等恳求
                if (isPlaying()) {
                    resumeOnFocusGain = true;
                    pause();
                }
                break;
        }
    }
}

音乐列表逻appreciate辑封装到MusicL艺术活动istManager:

public class MusicListManagerImpl implements MusicListManager, MusicPlayerListener {
    @Override
    public void setDatum(List<Song> datum) {
        //将本来数据playList标志设置为false
        DataUtil.changePlayListFlag(this.datum, false);
        //保存到数据库
        saveAll();
        //清空本来的数据
        this.datum.clear();
        //增加新的数据
        this.datum.addAll(datum);
        //更改播映列表标志
        DataUtil.changePlayListFlag(this.datum, true);
        //保存到数据库
        saveAll();
        sendPlayListChangedEvent(0);
    }
    /**
     * 保存播映列表
     */
    private void saveAll() {
        getOrm().saveAll(datum);
    }
    private LiteORMUtil getOrm() {
        return LiteORMUtil.getInstance(this.context);
    }
    @Override
    public void play(Song data) {
        //当时音乐黑胶唱片翻滚
        data.setRotate(true);
        //符号已经播映了
        isPlay = true;
        //保存数据
        this.data = data;
        if (StringUtils.isNotBlank(data.getPath())) {
            //本地音乐
            //不拼接地址
            musicPlayerManager.play(data.getPath(), data);
        } else {
            //判别是否有下载目标
            DownloadInfo downloadInfo = AppContext.getInstance().getDownloadManager().getDownloadById(data.getId());
            if (downloadInfo != null && downloadInfo.getStatus() == DownloadInfo.STATUS_COMPLETED) {
                //下载完结了
                //播映本地音乐
                musicPlayerManager.play(downloadInfo.getPath(), data);
                Timber.d("play offline %s %s %s", data.getTitle(), downloadInfo.getPath(), data.getUri());
            } else {
                //播映在线音乐
                String path = ResourceUtil.resourceUri(data.getUri());
                musicPlayerManager.play(path, data);
                Timber.d("play online %s %s", data.getTitle(), path);
            }
        }
        //设置终究播映音乐的Id
        sp.setLastPlaySongId(data.getId());
    }
    @Override
    public void pause() {
        musicPlayerManager.pause();
    }
    @Override
    public Song next() {
        if (datum.size() == 0) {
            //假如没有音乐了
            //直接回来null
            return null;
        }
        //音乐索引
        int index = 0;
        //判别循环形式
        switch (model) {
            case MODEL_LOOP_RANDOM:
                //随机循环
                //在0~datum.size()中
                //不包括datum.size()
                index = new Random().nextInt(datum.size());
                break;
            default:
                //找到当时音乐索引
                index = datum.indexOf(data);
                if (index != -1) {
                    //找到了
                    //假如当时播映是列表终究一个
                    if (index == datum.size() - 1) {
                        //终究一首音乐
                        //那就从0开端播映
                        index = 0;
                    } else {
                        index++;
                    }
                } else {
                    //抛出反常
                    //由于正常状况下是能找到的
                    throw new IllegalArgumentException("Cant'found current song");
                }
                break;
        }
        return datum.get(index);
    }
    @Override
    public void delete(int position) {
        //获取要删去的音乐
        Song song = datum.get(position);
        if (song.getId().equals(data.getId())) {
            //删去的音乐便是当时播映的音乐
            //应该中止当时播映
            pause();
            //并播映下一首音乐
            Song next = next();
            if (next.getId().equals(data.getId())) {
                //找到了自己
                //没有歌曲能够播映了
                data = null;
                //TODO Bug 随机循环的状况下有或许获取到自己
            } else {
                play(next);
            }
        }
        //直接删去
        datum.remove(song);
        //从数据库中删去
        getOrm().deleteSong(song);
        sendPlayListChangedEvent(position);
    }
    private void sendPlayListChangedEvent(int position) {
        EventBus.getDefault().post(new MusicPlayListChangedEvent(position));
    }
    /**
     * 播映完毕了回调
     *
     * @param mp
     */
    @Override
    public void onCompletion(MediaPlayer mp) {
        if (model == MODEL_LOOP_ONE) {
            //假如是单曲循环
            //就不会处理了
            //由于咱们运用了MediaPlayer的循环形式
            //假如运用的第三方结构
            //假如没有循环形式
            //那就要在这儿持续播映当时音乐
        } else {
            Song data = next();
            if (data != null) {
                play(data);
            }
        }
    }
   ...
}

外界一致运用播映列表管理器播映音乐,上一曲下一曲:

//播映按钮点击
binding.play.setOnClickListener(v -> {
    playOrPause();
});
//下一曲按钮点击
binding.next.setOnClickListener(v -> {
    getMusicListManager().play(getMusicListManager().next());
});
//播映列表按钮点击
binding.listButton.setOnClickListener(v -> {
    MusicPlayListDialogFragment.show(getSupportFragmentManager());
});

媒体操控器/桌面歌词/艺术字体转换器在线转换器桌面Widget

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
歌词完成了LRC,KSC两种歌词,封装到LyricListView,单个歌词行封装到LyricView中,外界直接运用LyricListView就行:

private void showLyricData() {
    binding.lyricList.setData(getMusicListManager().getData().getParsedLyric());
}

桌面歌词运用两个LyricView显现两行歌词,桌面歌词运用的是大局悬浮窗API艺术漆,所以要先判别是否有权限,没宫颈癌有需求先获取权限,json格式怎么打开然后才干显现,封装到GlobalLyricManagerImpl中:

/**
 * 大局(桌面)歌词管理器完成
 */
public class GlobalLyricManagerImpl implements GlobalLyricManager, MusicPlayerListener, GlobalLyricView.OnGlobalLyricDragListener, GlobalLyricView.GlobalLyricListener {
    public GlobalLyricManagerImpl(Context context) {
        this.context = context.getApplicationContext();
        //初始化偏好设置东西类
        sp = PreferenceUtil.getInstance(this.context);
        //初始化音乐播映管理器
        musicPlayerManager = MusicPlayerService.getMusicPlayerManager(this.context);
        //增加播映监听器
        musicPlayerManager.addMusicPlayerListener(this);
        //初始化窗口管理器
        initWindowManager();
        //从偏好设置中获取是否要显现大局歌词
        if (sp.isShowGlobalLyric()) {
            //创立大局歌词View
            initGlobalLyricView();
            //假如本来确定了歌词
            if (sp.isGlobalLyricLock()) {
                //确定歌词
                lock();
            }
        }
    }
    public synchronized static GlobalLyricManagerImpl getInstance(Context context) {
        if (instance == null) {
            instance = new GlobalLyricManagerImpl(context);
        }
        return instance;
    }
    /**
     * 确定大局歌词
     */
    private void lock() {
        //保存大局歌词确定状况
        sp.setGlobalLyricLock(true);
        //设置大局歌词控件状况
        setGlobalLyricStatus();
        //显现简略形式
        globalLyricView.simpleStyle();
        //更新布局
        updateView();
        //显现解锁大局歌词告诉
        NotificationUtil.showUnlockGlobalLyricNotification(context);
        //注册接纳解锁大局歌词广告接纳器
        registerUnlockGlobalLyricReceiver();
    }
    /**
     * 注册接纳解锁大局歌词广告接纳器
     */
    private void registerUnlockGlobalLyricReceiver() {
        if (unlockGlobalLyricBroadcastReceiver == null) {
            //创立播送接受者
            unlockGlobalLyricBroadcastReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (Constant.ACTION_UNLOCK_LYRIC.equals(intent.getAction())) {
                        //歌词解锁事情
                        unlock();
                    }
                }
            };
            IntentFilter intentFilter = new IntentFilter();
            //只监听歌词解锁事情
            intentFilter.addAction(Constant.ACTION_UNLOCK_LYRIC);
            //注册
            context.registerReceiver(unlockGlobalLyricBroadcastReceiver, intentFilter);
        }
    }
    /**
     * 解锁歌词
     */
    private void unlock() {
        //设置没有确定歌词
        sp.setGlobalLyricLock(false);
        //设置歌词状况
        setGlobalLyricStatus();
        //解锁后显现标准款式
        globalLyricView.normalStyle();
        //更新view
        updateView();
        //清除歌词解锁告诉
        NotificationUtil.clearUnlockGlobalLyricNotification(context);
        //免除接纳大局歌词事情播送接受者
        unregisterUnlockGlobalLyricReceiver();
    }
    /**
     * 免除接纳大局歌词事情播送接受者
     */
    private void unregisterUnlockGlobalLyricReceiver() {
        if (unlockGlobalLyricBroadcastReceiver != null) {
            context.unregisterReceiver(unlockGlobalLyricBroadcastReceiver);
            unlockGlobalLyricBroadcastReceiver = null;
        }
    }
    @Override
    public void show() {
        //检查大局悬浮窗权限
        if (!Settings.canDrawOverlays(context)) {
            Intent intent = new Intent(context, SplashActivity.class);
            intent.setAction(Constant.ACTION_LYRIC);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
            return;
        }
        //初始化大局歌词控件
        initGlobalLyricView();
        //设置显现了大局歌词
        sp.setShowGlobalLyric(true);
        WidgetUtil.onGlobalLyricShowStatusChanged(context, isShowing());
    }
    private boolean hasGlobalLyricView() {
        return globalLyricView != null;
    }
    /**
     * 大局歌词拖拽回调
     *
     * @param y y轴方向上移动的间隔
     */
    @Override
    public void onGlobalLyricDrag(int y) {
        layoutParams.y = y - SizeUtil.getStatusBarHeight(context);
        //更新view
        updateView();
        //保存歌词y坐标
        sp.setGlobalLyricViewY(layoutParams.y);
    }
    ...
}

显现艺术字和躲藏只json格式需求调用Go该管理器的相关办法就行了。

媒体操控器jsonobject

运用了能够经过体系媒体操控器,告诉栏,锁屏界面,耳机,蓝牙耳机艺术字体转换器在线转换器等设备操控媒体播映暂停,只需求把媒体信息更新到体系:json格式怎么打开

MusicPlayerServiapplece

/**
 * 更新媒体信息
 *
 * @param data
 * @param icon
 */
public void updateMetaData(Song data, Bitmap icon) {
    MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder()
            //标题
            .putString(MediaMetadataCompat.METADATA_KEY_TITLE, data.getTitle())
            //艺术家,也便是歌手
            .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, data.getSinger().getNickname())
            //专辑
            .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "专辑")
            //专辑艺术家
            .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, "专辑艺术家")
            //时长
            .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, data.getDuration())
            //封面
            .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, icon);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        //播映列表长度
        metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, musicListManager.getDatum().size());
    }
    mediaSession.setMetadata(metaData.build());
}

接纳媒体操控

/**
 * 媒体回调
 */
private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {
    @Override
    public void onPlay() {
        musicListManager.resume();
    }
    @Override
    public void onPause() {
        musicListManager.pause();
    }
    @Override
    public void onSkipToNext() {
        musicListManager.play(musicListManager.next());
    }
    @Override
    public void onSkipToPrevious() {
        musicListManager.play(musicListManager.previous());
    }
    @Override
    public void onSeekTo(long pos) {
        musicListManager.seekTo((int) pos);
    }
};

桌面艺术设计专业Widget

创立布局,然后注册,终究便是更新信息:

public class MusicWidget extends AppWidgetProvider {
    /**
     * 增加,从头运行运用,周期时刻,都会调用
     *
     * @param context
     * @param appWidgetManager
     * @param appWidgetIds
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        //测验发动service
        ServiceUtil.startService(context.getApplicationContext(), MusicPlayerService.class);
        //获取播映列表管理器
        MusicListManager musicListManager = MusicPlayerService.getListManager(context.getApplicationContext());
        //获取当时播映的音乐
        final Song data = musicListManager.getData();
        final int N = appWidgetIds.length;
        // 循环处理每一个,由于桌面上或许增加多个
        for (int i = 0; i < N; i++) {
            int appWidgetId = appWidgetIds[i];
            // 创立长途控件,所有对view的操作都必须经过该view供给的办法
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.music_widget);
            //由于这是在桌面的控件里边显现咱们的控件,所以不能直接经过setOnClickListener设置监听器
            //这儿发送的动作在MusicReceiver处理
            PendingIntent iconPendingIntent = IntentUtil.createMainActivityPendingIntent(context, Constant.ACTION_MUSIC_PLAYER_PAGE);
            //这儿直接发动service,也能够用播送接纳
            PendingIntent previousPendingIntent = IntentUtil.createMusicPlayerServicePendingIntent(context, Constant.ACTION_PREVIOUS);
            PendingIntent playPendingIntent = IntentUtil.createMusicPlayerServicePendingIntent(context, Constant.ACTION_PLAY);
            PendingIntent nextPendingIntent = IntentUtil.createMusicPlayerServicePendingIntent(context, Constant.ACTION_NEXT);
            PendingIntent lyricPendingIntent = IntentUtil.createMusicPlayerServicePendingIntent(context, Constant.ACTION_LYRIC);
            //设置点击事情
            views.setOnClickPendingIntent(R.id.icon, iconPendingIntent);
            views.setOnClickPendingIntent(R.id.previous, previousPendingIntent);
            views.setOnClickPendingIntent(R.id.play, playPendingIntent);
            views.setOnClickPendingIntent(R.id.next, nextPendingIntent);
            views.setOnClickPendingIntent(R.id.lyric, lyricPendingIntent);
            if (data == null) {
                //当时没有播映音乐
                appWidgetManager.updateAppWidget(appWidgetId, views);
            } else {
                //有播映音乐
                views.setTextViewText(R.id.title, String.format("%s - %s", data.getTitle(), data.getSinger().getNickname()));
                views.setProgressBar(R.id.progress, (int) data.getDuration(), (int) data.getProgress(), false);
                //显现图标
                RequestOptions options = new RequestOptions();
                options.centerCrop();
                Glide.with(context)
                        .asBitmap()
                        .load(ResourceUtil.resourceUri(data.getIcon()))
                        .apply(options)
                        .into(new CustomTarget<Bitmap>() {
                            @Override
                            public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                                //显现封面
                                views.setImageViewBitmap(R.id.icon, resource);
                                appWidgetManager.updateAppWidget(appWidgetId, views);
                            }
                            @Override
                            public void onLoadCleared(@Nullable Drawable placeholder) {
                                //显现默许图片
                                views.setImageViewBitmap(R.id.icon, BitmapFactory.decodeResource(context.getResources(), R.drawable.placeholder));
                                appWidgetManager.updateAppWidget(appWidgetId, views);
                            }
                        });
            }
        }
    }
}

登录/注册/验证码登录

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

登录注册没有多大难度,用户名和暗码登录,便是把信息传递到服务端,能够加密后在传输,服务端判别登录成功,回来一个符号,客户端保存,其他需求的登录的接口带上;验证码登录便是用验证码替代暗码,发送验证码都是服务端发送,客户端只需求调用接口。

谈论

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
谈论列表包括下拉刷jsonobject新,上拉加载更多,点赞,发布谈论,回复谈论,Emoji,论题和提醒人点击,挑选老友,appearance挑选论题等。

下拉刷新和下拉加载更多

中心逻辑就只需求更改page就行了

//下拉刷新监听器
binding.refresh.setOnRefreshListener(new OnRefreshListener() {
    @Override
    public void onRefresh(RefreshLayout refreshlayout) {
        loadData();
    }
});
//上拉加载更多
binding.refresh.setOnLoadMoreListener(new OnLoadMoreListener() {
    @Override
    public void onLoadMore(RefreshLayout refreshlayout) {
        loadMore();
    }
});
@Override
protected void loadData(boolean isPlaceholder) {
    super.loadData(isPlaceholder);
    isRefresh = true;
    pageMeta = null;
    loadMore();
}

提醒人二维码和论题点击

经过正则表达式,找到特殊文本,然后运用富文本完成点击。

holder.setText(R.id.content, processContent(data.getContent()));
/**
 * 处理文本点击事情
 * 这部分能够用监听器回调到Activity中处理
 *
 * @param content
 * @return
 */
private SpannableString processContent(String content) {
    //设置点击事情
    SpannableString result = RichUtil.processContent(getContext(), content,
            new RichUtil.OnTagClickListener() {
                @Override
                public void onTagClick(String data, RichUtil.MatchResult matchResult) {
                    String clickText = RichUtil.removePlaceholderString(data);
                    Timber.d("processContent mention click %s", clickText);
                    UserDetailActivity.startWithNickname(getContext(), clickText);
                }
            },
            (data, matchResult) -> {
                String clickText = RichUtil.removePlaceholderString(data);
                Timber.d("processContent hash tag %s", clickText);
            });
    //回来成果
    return result;
}

挑选老友

对数据分组,然后显现右jsonobject侧索引,挑选了经过EventBus发送到谈论界面。

adapter.setOnItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) {
            Object data = adapter.getItem(position);
            if (data instanceof User) {
                if (Constant.STYLE_FRIEND_SELECT == style) {
                    EventBus.getDefault().post(new SelectedFriendEvent((User) data));
                    //封闭界面
                    finish();
                } else {
                    startActivityExtraId(UserDetailActivity.class, ((User) data).getId());
                }
            }
        }
    });
}

视频和appstore播映

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

实在项艺术漆目中视频播映大部分都是用第三方服务,例如:阿里云视频服务,腾讯视频服务,由于他们供给一条龙服务,包括审核,转码,CDN,安全,播映器等,这儿用不到这么多功用,所以运用了第三方播映器播映普通mp4,这运用饺子播映器结构。

GSYVideoOptionBuilder videoOption = new GSYVideoOptionBuilder();
videoOption
//                .setThumbImageView(imageView)
        //小屏时不接触滑动
        .setIsTouchWiget(false)
        //音频焦点抵触时是否开释
        .setReleaseWhenLossAudio(true)
        .setRotateViewAuto(false)
        .setLockLand(false)
        .setAutoFullWithSize(true)
        .setSeekOnStart(seek)
        .setNeedLockFull(true)
        .setUrl(ResourceUtil.resourceUri(data.getUri()))
        .setCacheWithPlay(false)
        //全屏切换时不运用动画
        .setShowFullAnimation(false)
        .setVideoTitle(data.getTitle())
        //设置右下角 显现切换到全屏 的按键资源
        .setEnlargeImageRes(R.drawable.full_screen)
        //设置右下角 显现退出全屏 的按键资源
        .setShrinkImageRes(R.drawable.normal_screen)
        .setVideoAllCallBack(new GSYSampleCallBack() {
            @Override
            public void onPrepared(String url, Object... objects) {
                super.onPrepared(url, objects);
                //开端播映了才干旋转和全屏
                orientationUtils.setEnable(true);
                isPlay = true;
            }
            @Override
            public void onQuitFullscreen(String url, Object... objects) {
                super.onQuitFullscreen(url, objects);
                if (orientationUtils != null) {
                    orientationUtils.backToProtVideo();
                }
            }
        }).setLockClickListener(new LockClickListener() {
    @Override
    public void onClick(View view, boolean lock) {
        if (orientationUtils != null) {
            //合作下方的onConfigurationChanged
            orientationUtils.setEnable(!lock);
        }
    }
}).build(binding.player);
//开端播映
binding.player.startPlayLogic();

用户概况/更改材料

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

用户概况顶部显现用户信息,老友数量,下面别离显现创立的歌单,保藏的歌单,发json格式布的动appear态,相似微信朋友二维码支付代理圈,右上角能够更改用户材料;全体采用CoordinatorLayo艺术字体转换器在线转换器ut+TabLayo二维码ut+ViewPager+Fragment完成。

public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return UserDetailSheetFragment.newInstance(userId);
        case 1:
            return FeedFragment.newInstance(userId);
        default:
            return UserDetailAboutFragment.newInstance(userId);
    }
}
/**
 * 回来标题
 *
 * @param position
 * @return
 */
@Nullable
@Override
public CharSequence getPageTitle(int position) {
    //获取字符串id
    int resourceId = titleIds[position];
    //获取字符串
    return context.getResources().getString(resourceId);
}

发布动态/挑选方位/途径规划

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
发布作appetite用和微appreciate信朋友圈相似,能够挑选图片,和地理方位;地理方位运二维码套花呗秒到余额用高德地图完成挑选,途径规划是调用体系中安装的地图,相似微信。

挑选方位

/**
 * 查找该方位的poi,便利用户挑选,也便利其他人找
 * Point Of Interest,兴趣点)
 */
private void searchPOI(LatLng data, String keyword) {
    try {
        Timber.d("searchPOI %s %s", data, keyword);
        binding.progress.setVisibility(View.VISIBLE);
        adapter.setNewInstance(new ArrayList<>());
        // 第一个参数表明一个Latlng,第二参数表明范围多少米,第三个参数表明是火系坐标系仍是GPS原生坐标系
//        val query = RegeocodeQuery(
//            LatLonPoint(data.latitude, data.longitude)
//            , 1000F, GeocodeSearch.AMAP
//        )
//
//        geocoderSearch.getFromLocationAsyn(query)
        //keyWord表明查找字符串,
        //第二个参数表明POI查找类型,二者选填其一,选用POI查找类型时主张填写类型代码,码表能够参阅下方(而非文字)
        //cityCode表明POI查找区域,能够是城市编码也能够是城市称号,也能够传空字符串,空字符串代表全国在全国范围内进行查找
        PoiSearch.Query query = new PoiSearch.Query(keyword, "");
        query.setPageSize(10); // 设置每页最多回来多少条poiitem
        query.setPageNum(0); //设置查询页码
        PoiSearch poiSearch = new PoiSearch(this, query);
        poiSearch.setOnPoiSearchListener(this);
        //设置周边查找的中心点以及半径
        if (data != null) {
            poiSearch.setBound(new PoiSearch.SearchBound(
                    new LatLonPoint(
                            data.latitude,
                            data.longitude
                    ), 1000
            ));
        }
        poiSearch.searchPOIAsyn();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

高德地图途径规划

/**
 * 运用高德地图途径规划
 *
 * @param context
 * @param slat    起点纬度
 * @param slon    起点经度
 * @param sname   起点称号 可不填(0,0,null)
 * @param dlat    结尾纬度
 * @param dlon    结尾经度
 * @param dname   结尾称号 必填
 *                官方文档:https://lbs.amap.com/api/amap-mobile/guide/android/route
 */
public static void openAmapRoute(
        Context context,
        double slat,
        double slon,
        String sname,
        double dlat,
        double dlon,
        String dname
) {
    StringBuilder builder = new StringBuilder("amapuri://route/plan?");
    //第三方调用运用称号
    builder.append("sourceApplication=");
    builder.append(context.getString(R.string.app_name));
    //开端信息
    if (slat != 0.0) {
        builder.append("&sname=").append(sname);
        builder.append("&slat=").append(slat);
        builder.append("&slon=").append(slon);
    }
    //完毕信息
    builder.append("&dlat=").append(dlat)
            .append("&dlon=").append(dlon)
            .append("&dname=").append(dname)
            .append("&dev=0")
            .append("&t=0");
    startActivity(context, Constant.PACKAGE_MAP_AMAP, builder.toString());
}

谈天/离线推送

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
大部分实在项目中谈天都会挑选第三方商业级付费谈天服务,常用的有腾讯云谈APP天,融云谈天,网易云谈天等,这儿挑选融二维码生成器在线云谈天服务,运用步骤是先在服approve务端生appointment成谈天Token工龄越长退休金越多吗,这儿是登录后回来,然后客户端登录谈天服务器,然后设置音讯监听,发送音讯等。

登录谈天服务器

/**
 * 衔接谈天服务器
 *
 * @param data
 */
private void connectChat(Session data) {
    RongIMClient.connect(data.getChatToken(), new RongIMClient.ConnectCallback() {
        /**
         * 成功回调
         * @param userId 当时用户 ID
         */
        @Override
        public void onSuccess(String userId) {
            Timber.d("connect chat success %s", userId);
        }
        /**
         * 过错回调
         * @param errorCode 过错码
         */
        @Override
        public void onError(RongIMClient.ConnectionErrorCode errorCode) {
            Timber.e("connect chat error %s", errorCode);
            if (errorCode.equals(RongIMClient.ConnectionErrorCode.RC_CONN_TOKEN_INCORRECT)) {
                //从 APP 服务获取新 token,并重连
            } else {
                //无法衔接 IM 服务器,请根据相应的过错码作出对应处理
            }
            //由于咱们这个运用,不是相似微信那样纯谈天运用,所以谈天服务器衔接失利,也让进入运用
            //实在项目中按照需求完成就行了
            SuperToast.show(R.string.error_message_login);
        }
        /**
         * 数据库回调.
         * @param databaseOpenStatus 数据库翻开状况. DATABASE_OPEN_SUCCESS 数据库翻开成功; DATABASE_OPEN_ERROR 数据库翻开失利
         */
        @Override
        public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus databaseOpenStatus) {
        }
    });
}

设置音讯监听

chatClient.addOnReceiveMessageListener(new OnReceiveMessageWrapperListener() {
    @Override
    public void onReceivedMessage(Message message, ReceivedProfile profile) {
        //该办法的调用不再主线程
        Timber.e("chat onReceived %s", message);
        if (EventBus.getDefault().hasSubscriberForEvent(NewMessageEvent.class)) {
            //假如有监听该事情,表明在谈天界面,或许会话界面
            EventBus.getDefault().post(new NewMessageEvent(message));
        } else {
            handler.obtainMessage(0, message).sendToTarget();
        }
        //发送音讯未读数改变了告诉
        EventBus.getDefault().post(new MessageUnreadCountChangedEvent());
    }
});

发送文本音讯

发送图片等其他音讯也是差不多。

private void sendTextMessage() {
    String content = binding.input.getText().toString().trim();
    if (StringUtils.isEmpty(content)) {
        SuperToast.show(R.string.hint_enter_message);
        return;
    }
    TextMessage textMessage = TextMessage.obtain(content);
    RongIMClient.getInstance().sendMessage(Conversation.ConversationType.PRIVATE, targetId, textMessage, null, MessageUtil.createPushData(MessageUtil.getContent(textMessage), sp.getUserId()), new IRongCallback.ISendMessageCallback() {
        @Override
        public void onAttached(Message message) {
            // 音讯成功存到本地数据库的回调
            Timber.d("sendTextMessage onAttached %s", message);
        }
        @Override
        public void onSuccess(Message message) {
            // 音讯发送成功的回调
            Timber.d("sendTextMessage success %s", message);
            //清空输入框
            clearInput();
            addMessage(message);
        }
        @Override
        public void onError(Message message, RongIMClient.ErrorCode errorCode) {
            // 音讯发送失利的回调
            Timber.e("sendTextMessage onError %s %s", message, errorCode);
        }
    });
}

离线推送

先敞开SDK离线推送,还要别离去厂商那儿申请推送装备,这儿只完成了小米推送,其他的华为推送,OPPappstoreO推送等差不多;然后把推送,或许二维码生成器在线点击都一致代理到主界面,然后再处理。

private void postRun(Intent intent) {
    String action = intent.getAction();
    if (Constant.ACTION_CHAT.equals(action)) {
        //本地显现的音讯告诉点击
        //要跳转到谈天界面
        String id = intent.getStringExtra(Constant.ID);
        startActivityExtraId(ChatActivity.class, id);
    } else if (Constant.ACTION_PUSH.equals(action)) {
        //谈天告诉点击
        String id = intent.getStringExtra(Constant.PUSH);
        startActivityExtraId(ChatActivity.class, id);
    }
}

商城/订单/付出/购物车json是什么意思

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

学到这儿,咱们不能说熟悉,那么看到上面的界面,那么大体要能完成出来。

产品概况富文本

//概况
HtmlText.from(data.getDetail())
    .setImageLoader(new HtmlImageLoader() {
        @Override
        public void loadImage(String url, final Callback callback) {
            Glide.with(getHostActivity())
                    .asBitmap()
                    .load(url)
                    .into(new CustomTarget<Bitmap>() {
                        @Override
                        public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                            callback.onLoadComplete(resource);
                        }
                        @Override
                        public void onLoadCleared(@Nullable Drawable placeholder) {
                            callback.onLoadFailed();
                        }
                    });
        }
        @Override
        public Drawable getDefaultDrawable() {
            return ContextCompat.getDrawable(getHostActivity(), R.drawable.placeholder);
        }
        @Override
        public Drawable getErrorDrawable() {
            return ContextCompat.getDrawable(getHostActivity(), R.drawable.placeholder_error);
        }
        @Override
        public int getMaxWidth() {
            return ScreenUtil.getScreenWith(getHostActivity());
        }
        @Override
        public boolean fitWidth() {
            return true;
        }
    })
    .setOnTagClickListener(new OnTagClickListener() {
        @Override
        public void onImageClick(Context context, List<String> imageUrlList, int position) {
            // image click
        }
        @Override
        public void onLinkClick(Context context, String url) {
            // link click
            Timber.d("onLinkClick %s", url);
        }
    })
    .into(binding.detail);

付出

客户端先集成工龄差一年工资差多少微信,付出宝SDK,然后恳求服务端获取付出json文件是干什么的信息,设置到SDK,终究便是处理付出成果。

/**
 * 处理付出宝付出
 *
 * @param data
 */
private void processAlipay(String data) {
    PayUtil.alipay(getHostActivity(), data);
}
/**
 * 处理微信付出
 *
 * @param data
 */
private void processWechat(WechatPay data) {
    //把服务端回来的参数
    //设置到对应的字段
    PayReq request = new PayReq();
    request.appId = data.getAppid();
    request.partnerId = data.getPartnerid();
    request.prepayId = data.getPrepayid();
    request.nonceStr = data.getNoncestr();
    request.timeStamp = data.getTimestamp();
    request.packageValue = data.getPackageValue();
    request.sign = data.getSign();
    AppContext.getInstance().getWxapi().sendReq(request);
}

处理付出成果

/**
 * 付出宝付出状况改变了
 *
 * @param event
 */
@Subscribe(threadMode = ThreadMode.MAIN)
public void onAlipayStatusChanged(AlipayStatusChangedEvent event) {
    String resultStatus = event.getData().getResultStatus();
    if ("9000".equals(resultStatus)) {
        //本地付出成功
        //不能依靠本地付出成果
        //必定要以服务端为准
        showLoading(R.string.hint_pay_wait);
        //延时3秒
        //由于付出宝回调咱们服务端或许有延迟
        binding.primary.postDelayed(() -> {
            checkPayStatus();
        }, 3000);
    } else if ("6001".equals(resultStatus)) {
        //付出取消
        SuperToast.show(R.string.error_pay_cancel);
    } else {
        //付出失利
        SuperToast.show(R.string.error_pay_failed);
    }
}

语音辨认输入地址

这儿运用百度语音辨认SDK,先集成,然后初始化,终究是监听辨认成果:

/**
 * 百度语音辨认事情监听器
 * <p>
 * https://ai.baidu.com/ai-doc/SPEECH/4khq3iy52
 */
EventListener voiceRecognitionEventListener = new EventListener() {
    /**
     * 事情回调
     * @param name 回调事情称号
     * @param params 回调参数
     * @param data 数据
     * @param offset 开端方位
     * @param length 长度
     */
    @Override
    public void onEvent(String name, String params, byte[] data, int offset, int length) {
        String result = "name: " + name;
        if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_READY)) {
            // 引擎就绪,能够说话,一般在收到此事情后经过UI告诉用户能够说话了
            setStopVoiceRecognition();
        } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL)) {
            // 一句话的临时成果,终究成果及语义成果
            if (params == null || params.isEmpty()) {
                return;
            }
            // 辨认相关的成果都在这儿
            try {
                JSONObject paramObject = new JSONObject(params);
                //获取第一个成果
                JSONArray resultsRecognition = paramObject.getJSONArray("results_recognition");
                String voiceRecognitionResult = resultsRecognition.getString(0);
                //能够根据result_type是临时成果,仍是终究成果
                binding.input.setText(voiceRecognitionResult);
                result += voiceRecognitionResult;
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_FINISH)) {
            //一句话辨认完毕(或许含有过错信息) 。终究辨认的文字成果在ASR_PARTIAL事情中
            if (params.contains(""error":0")) {
            } else if (params.contains(""error":7")) {
                SuperToast.show(R.string.voice_error_no_result);
            } else {
                //其他过错
                SuperToast.show(getString(R.string.voice_error, params));
            }
        } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_EXIT)) {
            //辨认完毕,资源开释
            setStartVoiceRecognition();
        }
        Timber.d("baidu voice recognition onEvent %s", result);
    }
};

百度OCR

运用百度OCR从图片中辨认文本,主要是辨认地址,相似顺丰大众号输入地址时辨认功用。

private void recognitionImage(String data) {
    GeneralBasicParams param = new GeneralBasicParams();
    param.setDetectDirection(true);
    param.setImageFile(new File(data));
    // 调用通用文字辨认服务
    OCR.getInstance(getApplicationContext()).recognizeGeneralBasic(param, new OnResultListener<GeneralResult>() {
        /**
         * 成功
         * @param result
         */
        @Override
        public void onResult(GeneralResult result) {
            StringBuilder builder = new StringBuilder();
            for (WordSimple it : result.getWordList()) {
                builder.append(it.getWords());
                //每一项之间,增加空格,便利OCR失利
                builder.append(" ");
            }
            binding.input.setText(builder.toString());
        }
        /**
         * 失利
         * @param error
         */
        @Override
        public void onError(OCRError error) {
            SuperToast.show(getString(R.string.ocr_error, error.getMessage(), error.getErrorCode()));
        }
    });
}

还有一些功用,例如:快捷枸杞方式二维码是谁发明等就不在贴代码了。